WebFlux — reactive stack (non-blocking), MVC — classic stack (blocking). The choice depends on the task.
Simple analogy:
Why it matters: The choice between MVC and WebFlux determines your application's fundamental architecture, thread model, debugging experience, and maximum throughput. Choosing wrong means either over-engineering a simple CRUD app (WebFlux) or hitting a performance ceiling on a high-throughput service (MVC without virtual threads).
Comparison: | Feature | MVC | WebFlux | |---|---|---| | Model | Thread-per-request | Event-loop (Netty) | | Blocking | JDBC blocks a thread | R2DBC non-blocking | | Simplicity | Higher | Lower | | Throughput | Limited by threads | Very high | | Debugging | Stack trace is clear | Reactor stack trace is complex | | Virtual Threads | Makes this a viable alternative | Already non-blocking | | Learning curve | Low | High (Reactor, Mono/Flux) | | Testing | Standard JUnit | StepVerifier required |
WebFlux (Reactor):
1@RestController2public class UserController {3 @GetMapping("/users/{id}")4 public Mono<User> getUser(@PathVariable Long id) {5 return userRepository.findById(id)6 .switchIfEmpty(Mono.error(new UserNotFoundException(id)));7 }89 @GetMapping("/users")10 public Flux<User> getAllUsers() {11 return userRepository.findAll()12 .filter(User::isActive)13 .take(100);14 }1516 @GetMapping("/users/{id}/posts")17 public Mono<Map<String, Object>> getUserWithPosts(@PathVariable Long id) {18 return userRepository.findById(id)19 .zipWith(postRepository.findByUserId(id).collectList())20 .map(tuple -> Map.of("user", tuple.getT1(), "posts", tuple.getT2()));21 }22}
When to choose WebFlux:
When to choose MVC (+ Virtual Threads):
Virtual Threads (Java 21+) — the middle ground:
1spring:2 threads:3 virtual:4 enabled: true # Spring Boot 3.2+
Same throughput as WebFlux, but with regular MVC code! One million virtual threads can handle one million concurrent connections, even with blocking JDBC calls, because the JVM park/unpark operation is extremely cheap.
Common mistake: Choosing WebFlux just for "higher performance" without understanding the tradeoffs. WebFlux is harder to debug, the entire stack must be non-blocking (including database drivers), and it provides no benefit if your bottleneck is a single slow database query.