Spring Boot simplifies the development of reactive web applications by providing auto-configuration for Spring Webflux.
Spring WebFlux Framework
Spring WebFlux is a new reactive web framework introduced in Spring Framework 5.0. Unlike Spring MVC, it does not require a servlet API, is completely asynchronous and non-blocking, and implements the Reactive Streams via Project Reactor.
Spring WebFlux comes in two flavors: functional model-based and based on annotations. The annotation-based model is quite close to the Spring MVC model, as shown in the following example:
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/users")
public class MyRestController {
private final UserRepository userRepository;
private final CustomerRepository customerRepository;
public MyRestController(UserRepository userRepository, CustomerRepository customerRepository) {
this.userRepository = userRepository;
this.customerRepository = customerRepository;
}
@GetMapping("/{userId}")
public Mono<User> getUser(@PathVariable Long userId) {
return this.userRepository.findById(userId);
}
@GetMapping("/{userId}/customers")
public Flux<Customer> getUserCustomers(@PathVariable Long userId) {
return this.userRepository.findById(userId).flatMapMany(this.customerRepository::findByUser);
}
@DeleteMapping("/{userId}")
public Mono<Void> deleteUser(@PathVariable Long userId) {
return this.userRepository.deleteById(userId);
}
}
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
@RestController
@RequestMapping("/users")
class MyRestController(private val userRepository: UserRepository, private val customerRepository: CustomerRepository) {
@GetMapping("/{userId}")
fun getUser(@PathVariable userId: Long): Mono<User?> {
return userRepository.findById(userId)
}
@GetMapping("/{userId}/customers")
fun getUserCustomers(@PathVariable userId: Long): Flux<Customer> {
return userRepository.findById(userId).flatMapMany { user: User? ->
customerRepository.findByUser(user)
}
}
@DeleteMapping("/{userId}")
fun deleteUser(@PathVariable userId: Long): Mono<Void> {
return userRepository.deleteById(userId)
}
}
"WebFlux.fn", a variant based on the functional model, decouples the routing configuration from actual request processing, as shown in the following example:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicate;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
@Configuration(proxyBeanMethods = false)
public class MyRoutingConfiguration {
private static final RequestPredicate ACCEPT_JSON = accept(MediaType.APPLICATION_JSON);
@Bean
public RouterFunction<ServerResponse> monoRouterFunction(MyUserHandler userHandler) {
return route()
.GET("/{user}", ACCEPT_JSON, userHandler::getUser)
.GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers)
.DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser)
.build();
}
}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.MediaType
import org.springframework.web.reactive.function.server.RequestPredicates.DELETE
import org.springframework.web.reactive.function.server.RequestPredicates.GET
import org.springframework.web.reactive.function.server.RequestPredicates.accept
import org.springframework.web.reactive.function.server.RouterFunction
import org.springframework.web.reactive.function.server.RouterFunctions
import org.springframework.web.reactive.function.server.ServerResponse
@Configuration(proxyBeanMethods = false)
class MyRoutingConfiguration {
@Bean
fun monoRouterFunction(userHandler: MyUserHandler): RouterFunction<ServerResponse> {
return RouterFunctions.route(
GET("/{user}").and(ACCEPT_JSON), userHandler::getUser).andRoute(
GET("/{user}/customers").and(ACCEPT_JSON), userHandler::getUserCustomers).andRoute(
DELETE("/{user}").and(ACCEPT_JSON), userHandler::deleteUser)
}
companion object {
private val ACCEPT_JSON = accept(MediaType.APPLICATION_JSON)
}
}
import reactor.core.publisher.Mono;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
@Component
public class MyUserHandler {
public Mono<ServerResponse> getUser(ServerRequest request) {
...
}
public Mono<ServerResponse> getUserCustomers(ServerRequest request) {
...
}
public Mono<ServerResponse> deleteUser(ServerRequest request) {
...
}
}
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse
import reactor.core.publisher.Mono
@Component
class MyUserHandler {
fun getUser(request: ServerRequest?): Mono<ServerResponse> {
return ServerResponse.ok().build()
}
fun getUserCustomers(request: ServerRequest?): Mono<ServerResponse> {
return ServerResponse.ok().build()
}
fun deleteUser(request: ServerRequest?): Mono<ServerResponse> {
return ServerResponse.ok().build()
}
}
RouterFunction
beans as you wish. Beans can be ordered if you want to use execution order.
To get started, add the spring-boot-starter-webflux
module to your application.
spring-boot-starter-web
and
spring-boot-starter-webflux
modules to your application will cause Spring Boot will automatically configure Spring MVC, not WebFlux. This logic was chosen because many Spring developers add
spring-boot-starter-webflux
to their Spring MVC applications to use a reactive
WebClient
. You can still choose yourself by setting the selected application type in
SpringApplication.setWebApplicationType(WebApplicationType.REACTIVE)
.
Spring WebFlux auto-configuration
Spring Boot provides auto-configuration for Spring WebFlux, which works great with most applications.
Autoconfiguration brings the following features in addition to Spring's default settings:
Configuring codecs for
HttpMessageReader
andHttpMessageWriter
instances.Support for handling static resources, including support for WebJar.
If you want to retain the capabilities of Spring Boot WebFlux and add additional WebFlux configuration, you can add your own class marked with the @Configuration
annotation like WebFluxConfigurer
, but without @EnableWebFlux
annotations.
If you want to have full control over Spring WebFlux, you can add your own @Configuration
annotated with @EnableWebFlux
.
HTTP codecs via HttpMessageReaders and HttpMessageWriters
Spring WebFlux uses the HttpMessageReader
and HttpMessageWriter
interfaces to translate requests and HTTP responses. These are configured using CodecConfigurer
with adequate values by looking at the libraries available in your classpath.
Spring Boot provides specialized configuration properties for codecs, spring.codec.*
. The framework also applies further customization through CodecCustomizer
instances. For example, the configuration keys spring.jackson.*
are applied to the Jackson library codec.
If you need to add or configure codecs, you can create a custom component CodecCustomizer
, as shown in the following example:
import org.springframework.boot.web.codec.CodecCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.codec.ServerSentEventHttpMessageReader;
@Configuration(proxyBeanMethods = false)
public class MyCodecsConfiguration {
@Bean
public CodecCustomizer myCodecCustomizer() {
return (configurer) -> {
configurer.registerDefaults(false);
configurer.customCodecs().register(new ServerSentEventHttpMessageReader());
// ...
};
}
}
import org.springframework.boot.web.codec.CodecCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.http.codec.CodecConfigurer
import org.springframework.http.codec.ServerSentEventHttpMessageReader
class MyCodecsConfiguration {
@Bean
fun myCodecCustomizer(): CodecCustomizer {
return CodecCustomizer { configurer: CodecConfigurer ->
configurer.registerDefaults(false)
configurer.customCodecs().register(ServerSentEventHttpMessageReader())
}
}
}
In addition, you can use custom JSON serializers and deserializers in Spring Boot.
Static content
By default, Spring Boot processes static content from the /static
directory (or /public
, or /resources
, or /META-INF/resources
) in the classpath. The framework uses ResourceWebHandler
from Spring WebFlux, so you can change this logic by adding your own WebFluxConfigurer
and overriding the addResourceHandlers
method.
By default, resources are mapped to /**
, but you can fine-tune this mapping by setting the spring.webflux.static-path-pattern
property. For example, moving all resources to /resources/**
can be done as follows:
spring.webflux.static-path-pattern=/resources/**
spring:
webflux:
static-path-pattern: "/resources/**"
In addition, you can configure the locations of static resources using spring.web.resources.static-locations
. This replaces the default values with a list of directory locations. With these settings, the default home page detection will switch to your custom locations. So, if there is index.html
in any of your locations when you launch, that will become the app's home page.
In addition to the "standard" static resource location locations listed earlier, a special script is provided for Webjars content. Any resources with a path in /webjars/**
are processed from jar files if they are packaged in the Webjars format.
src/main/webapp
directory.
Home Page
Spring Boot supports both static and templated start pages. The framework first looks for the index.html
file in the configured static content locations. If no such pattern is found, it looks for the index
pattern. If one of them is found, it will automatically be used as the application's start page.
Template engines
In addition to REST web services, you can also use Spring WebFlux to handle dynamic HTML content. Spring WebFlux supports various templating technologies, including Thymeleaf, FreeMarker and Mustache.
Spring Boot provides auto-configuration support for the following templating engines:
If one of these template engines is used with the default configuration, then the templates are automatically selected from src/main/resources/templates
.
Error Handling
Spring Boot provides a WebExceptionHandler
that handles all errors appropriately. Its position in the processing order is immediately before the handlers provided by WebFlux, which are counted last. For machine clients, it generates a JSON response with a detailed description of the error, an HTTP status code, and an exception message. For browser clients, there is a "whitelabel" error handler that renders the same data in HTML format. You can also provide your own HTML templates for displaying errors.
The first step in setting up this feature is often to use the existing mechanism, except replacing or adding to the error content. To do this, you can add a bean of type ErrorAttributes
.
To change the error handling logic, you can implement ErrorWebExceptionHandler
and register a bean definition of this type. Because ErrorWebExceptionHandler
is quite low-level, Spring Boot also provides a helper AbstractErrorWebExceptionHandler
that allows you to handle errors in a functional way through WebFlux, as shown in the following example:
import reactor.core.publisher.Mono;
import org.springframework.boot.autoconfigure.web.WebProperties.Resources;
import org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.function.server.ServerResponse.BodyBuilder;
@Component
public class MyErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {
public MyErrorWebExceptionHandler(ErrorAttributes errorAttributes, Resources resources,
ApplicationContext applicationContext) {
super(errorAttributes, resources, applicationContext);
}
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(this::acceptsXml, this::handleErrorAsXml);
}
private boolean acceptsXml(ServerRequest request) {
return request.headers().accept().contains(MediaType.APPLICATION_XML);
}
public Mono<ServerResponse> handleErrorAsXml(ServerRequest request) {
BodyBuilder builder = ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR);
// ... additional calls to the build tool return builder.build();
return builder.build();
}
}
import org.springframework.boot.autoconfigure.web.WebProperties
import org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler
import org.springframework.boot.web.reactive.error.ErrorAttributes
import org.springframework.context.ApplicationContext
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.server.RouterFunction
import org.springframework.web.reactive.function.server.RouterFunctions
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse
import reactor.core.publisher.Mono
@Component
class MyErrorWebExceptionHandler(errorAttributes: ErrorAttributes?, resources: WebProperties.Resources?,
applicationContext: ApplicationContext?) : AbstractErrorWebExceptionHandler(errorAttributes, resources, applicationContext) {
override fun getRoutingFunction(errorAttributes: ErrorAttributes): RouterFunction<ServerResponse> {
return RouterFunctions.route(this::acceptsXml, this::handleErrorAsXml)
}
private fun acceptsXml(request: ServerRequest): Boolean {
return request.headers().accept().contains(MediaType.APPLICATION_XML)
}
fun handleErrorAsXml(request: ServerRequest?): Mono<ServerResponse> {
val builder = ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR)
// ... additional build tool calls return builder.build()
return builder.build()
}
}
For a more complete picture, you can also subclass DefaultErrorWebExceptionHandler
directly and override specific methods.
In some cases, errors handled at the controller or handler function level are not captured by the metrics framework . Applications can ensure that such exceptions are recorded in request metrics by configuring the handled exception as a request attribute:
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.reactive.result.view.Rendering;
import org.springframework.web.server.ServerWebExchange;
@Controller
public class MyExceptionHandlingController {
@GetMapping("/profile")
public Rendering userProfile() {
// ...
throw new IllegalStateException();
}
@ExceptionHandler(IllegalStateException.class)
public Rendering handleIllegalState(ServerWebExchange exchange, IllegalStateException exc) {
exchange.getAttributes().putIfAbsent(ErrorAttributes.ERROR_ATTRIBUTE, exc);
return Rendering.view("errorView").modelAttribute("message", exc.getMessage()).build();
}
}
import org.springframework.boot.web.reactive.error.ErrorAttributes
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.reactive.result.view.Rendering
import org.springframework.web.server.ServerWebExchange
@Controller
class MyExceptionHandlingController {
@GetMapping("/profile")
fun userProfile(): Rendering {
// ...
throw IllegalStateException()
}
@ExceptionHandler(IllegalStateException::class)
fun handleIllegalState(exchange: ServerWebExchange, exc: IllegalStateException): Rendering {
exchange.attributes.putIfAbsent(ErrorAttributes.ERROR_ATTRIBUTE, exc)
return Rendering.view("errorView").modelAttribute("message", exc.message ?: "").build()
}
}
Custom error pages
If you want to display a custom HTML error page for a given status code, you can add a file to the /error
directory. Error pages can be either static HTML (that is, added to any of the static resource directories) or created using templates. The file name must be the exact status code or series mask.
For example, to display a 404
with a static HTML file, the directory structure should look like this:
src/
+- main/
+- java/
| + <source code>
+- resources/
+- public/
+- error/
| +- 404.html
+- <other public assets>
To display all 5xx
errors using the Mustache template, structure directory should look like this:
src/
+- main/
+- java/
| + <source code>
+- resources/
+- templates/
+- error/
| +- 5xx.mustache
+- <other templates>
Web filters
Spring WebFlux provides an interface WebFilter
, which can be implemented to filter HTTP request and response exchanges. WebFilter
beans found in the application context will be automatically used to filter each instance of communication.
If the order of the filters matters, they can implement the Ordered
class or they can also be marked with the annotation @Order
. Spring Boot auto-configuration can configure web filters for you. In this case, the ordering methods shown in the following table will be used:
Web Filter | Order |
---|---|
|
|
|
|
|
|
GO TO FULL VERSION