The full picture
| Dimension | MVC (+ virtual threads) | WebFlux |
|---|---|---|
| Programming model | imperative, blocking-style | Reactor: Mono/Flux pipelines everywhere (viral) |
| Scalability limiter | was threads; with VTs → downstream capacity | event loop discipline (one blocking call stalls a core) |
| Ecosystem constraint | any JDBC/clients | needs R2DBC, reactive clients end-to-end |
| Debugging/on-call | normal stack traces, thread dumps | assembly vs execution time, operator fusion — training required |
| Backpressure | none native (queue/timeout at edges) | first-class (request(n) protocol) |
- The classic WebFlux failure mode: one blocking JDBC call or heavyweight computation on an event loop degrades the entire instance — and it's invisible in code review unless the team is trained (BlockHound exists for a reason).
- The classic MVC failure mode was pool exhaustion under slow downstreams — the thing virtual threads largely dissolve (with the caveat that DB pools and downstreams still cap real throughput).
- Where WebFlux/reactive is still the right call: high-fan-out aggregation gateways, streaming (SSE, live feeds), backpressure-sensitive pipelines, and Kafka-to-socket style flows. WebClient, notably, is worth using even from MVC.
- Team factor is architectural: a reactive codebase is a hiring and on-boarding decision, not just a runtime one.