13.1 Overview of Spring WebFlux and Comparison with MVC
Introduced in Spring 5.0, Spring WebFlux is an asynchronous, non-blocking reactive web framework. This section explores how it differs from traditional Spring MVC and why it is necessary.
1. Background of WebFlux
Standard Spring MVC (Servlet-based) uses a Thread-per-Request model, where each request is assigned its own thread. This approach has limitations: as concurrent users increase, the thread count rises, leading to high memory consumption and significant context-switching overhead.
WebFlux was created to handle massive numbers of concurrent connections efficiently using a small number of threads.
Core Concept: Reactive Programming
Reactive programming is a programming paradigm focused on data streams and the propagation of change. It operates by pushing data to consumers whenever it is produced, with asynchronous processing as a default.
2. Key Comparison: Spring MVC vs. Spring WebFlux
| Feature | Spring MVC | Spring WebFlux |
|---|---|---|
| Model | Synchronous + Blocking | Asynchronous + Non-blocking |
| Processing | Thread-per-request | Event Loop (High concurrency with fewer threads) |
| Stack | Servlet API (Tomcat, etc.) | Reactive Streams (Netty, etc.) |
| Database | JDBC (Blocking) | R2DBC, NoSQL (Non-blocking) |
| Recommended Use | Standard web apps, typical CRUD | Streaming, Chatting, high server-to-server calls (MSA) |
3. Reactive Streams (Mono and Flux)
To understand WebFlux, you must know the two core types provided by the Reactor library:
Project Reactor and Publisher-Subscriber Pattern
Reactor is a library that implements the Reactive Streams standard, defining the relationship between the Publisher(who emits data) and the ** Subscriber**(who consumes it).
- Subscribe: The Subscriber subscribes to the Publisher.
- Request: The Subscriber requests a specific number of data items from the Publisher (** Backpressure**).
- onNext: The Publisher delivers the data.
- onComplete / onError: The process finishes when all data is delivered or an error occurs.
Core Data Types: Mono and Flux
Mono<T>: Emits 0 or 1 item (Single response).Flux<T>: Emits 0 to N items (Multiple responses, streaming).
// Mono Example: Emits "HONG" upon success and then completes
Mono<String> name = Mono.just("Hong")
.map(String::toUpperCase)
.flatMap(s -> Mono.just("Name: " + s));
// Flux Example: Useful for multiple items (Use instead of List)
Flux<Integer> numbers = Flux.just(1, 2, 3, 4, 5)
.filter(n -> n % 2 == 0)
.map(n -> n * 10);
4. Controller Implementation Styles
WebFlux supports both the traditional @Controller approach (Annotated Controller) and a newer functional model (Functional Endpoints).
1) Annotated Controller
Familiar to MVC users, but returning types must be Mono or Flux.
@RestController
public class HelloController {
@GetMapping("/hello")
public Mono<String> hello() {
return Mono.just("Hello, WebFlux!");
}
}
2) Functional Endpoints
Separates Routing and Handling logic for more declarative endpoint definitions.
@Component
public class HelloHandler {
public Mono<ServerResponse> hello(ServerRequest request) {
return ServerResponse.ok().bodyValue("Hello, Reactive!");
}
}
@Configuration
public class HelloRouter {
@Bean
public RouterFunction<ServerResponse> route(HelloHandler handler) {
return RouterFunctions.route(RequestPredicates.GET("/hello"), handler::hello);
}
}
5. Practical Considerations
- Non-blocking Chain: Including blocking code (e.g., standard JDBC,
Thread.sleep) in a WebFlux environment will drastically degrade performance. Every library in the chain must support non-blocking. - Multi Event Loop: WebFlux uses a multi-event loop model to process thousands of requests with a small number of fixed threads (usually CPU cores * 2). Be careful not to let a specific request occupy a thread for too long.
- Backpressure Management: It includes mechanisms to control load when the consumer's speed is slower than the producer's, enhancing system stability.
- Learning Curve: Reactive programming requires a shift in mindset compared to imperative programming and can be harder to debug.