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:

Java
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);
    }
}
Kotlin
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:

Java
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();
    }
}
Kotlin
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)
    }
}
Java
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) {
        ...
    }
}
Kotlin
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()
    }
}
To modulate the router definition, you can define as many 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.

Adding the 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 and HttpMessageWriter 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:

Java
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());
            // ...
        };
    }
}
Kotlin
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:

Properties
spring.webflux.static-path-pattern=/resources/**
Yaml
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.

Spring WebFlux applications do not depend on the servlet API, so they can be deployed as war files and will not use the 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:

Java
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();
    }
}
Kotlin
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:

Java
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();
    }
}
Kotlin
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

MetricsWebFilter

Ordered.HIGHEST_PRECEDENCE + 1

WebFilterChainProxy (Spring Security)

-100

HttpTraceWebFilter

Ordered.LOWEST_PRECEDENCE - 10