Spring WebFlux Basics

Introduction

Spring WebFlux is the reactive web stack on Netty—non-blocking HTTP with Mono and Flux return types. Use it for high-concurrency IO or streaming endpoints. This chapter builds annotation-style controllers, compares WebFlux with Spring MVC, and handles errors in reactive APIs.

Prerequisites

Add WebFlux

Replace or add (avoid running MVC + WebFlux in same app without clear split):

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

Remove spring-boot-starter-web if building pure WebFlux app—Boot picks one primary web stack.

Reactive Controller

Code explanation:

  • Return Mono<T> for single JSON body
  • Return Flux<T> for array stream (JSON array when collected)

Service Layer

Replace in-memory store with R2DBC repository in production—chapter 27.

Functional Routing (Alternative)

java
@Configuration
public class RouterConfig {
 
    @Bean
    public RouterFunction<ServerResponse> itemRoutes(ItemHandler handler) {
        return RouterFunctions.route()
            .GET("/api/items", handler::list)
            .GET("/api/items/{id}", handler::get)
            .POST("/api/items", handler::create)
            .build();
    }
}

Annotation style maps cleanly from MVC—functional style composes routes explicitly.

Global Error Handling

java
@RestControllerAdvice
public class ReactiveExceptionHandler {
 
    @ExceptionHandler(IllegalArgumentException.class)
    public Mono<ResponseEntity<ApiError>> handleBadRequest(IllegalArgumentException ex) {
        return Mono.just(ResponseEntity.badRequest()
            .body(new ApiError("BAD_REQUEST", ex.getMessage())));
    }
 
    public record ApiError(String code, String message) {}
}

For WebExceptionHandler in pure functional apps—similar role to @RestControllerAdvice.

WebFlux vs Spring MVC

Spring MVCWebFlux
ServerTomcat/Jetty (Servlet)Netty (default)
Thread modelThread per requestEvent loop + small pool
API styleBlockingMono/Flux
JDBCNaturalBlock on boundedElastic or use R2DBC
Ecosystem maturityWidestGrowing

Tip

Default to MVC Unless You Have a Measured Need

Team productivity and JDBC/JPA familiarity often outweigh theoretical concurrency gains.

Test With WebTestClient

FAQ

404 on same paths as MVC tutorial?

Different starter—confirm WebFlux on classpath and controller scanned.

Block JDBC in WebFlux?

Wrap with Mono.fromCallable(() -> jdbc).subscribeOn(Schedulers.boundedElastic())—anti-pattern at scale; prefer R2DBC.

SSE streaming?

produces = MediaType.TEXT_EVENT_STREAM_VALUE return Flux<ServerSentEvent>.

File upload reactive?

FilePart in multipart—different from MVC MultipartFile.

Spring Security reactive?

ServerHttpSecurity bean—chapter 27.

Port still 8080?

Yes—server.port same property.