Spring MVC is blocking and thread-per-request; WebFlux is non-blocking and runs on a small event loop. Neither is universally "better" — the choice is driven by your I/O profile and how reactive your dependencies are. The honest senior answer rejects "WebFlux is faster" and reaches for nuance.
How each handles a request
Spring MVC (Servlet stack) assigns one thread to a request for its entire lifetime. While that thread waits on a database or downstream call, it's parked — consuming a thread and its stack memory. Concurrency is capped by the thread pool size; thousands of slow concurrent requests mean thousands of threads (or queuing/timeouts).
WebFlux (Reactor + Netty) runs on a few event-loop threads (roughly one per CPU core). A request that hits I/O releases the thread immediately and resumes via callback when data is ready. A handful of threads multiplex thousands of connections — as long as nothing blocks them.
// MVC: blocking, thread held until DB returns
@GetMapping("/u/{id}") User get(@PathVariable Long id) { return repo.findById(id); }
// WebFlux: non-blocking, thread freed during the DB call
@GetMapping("/u/{id}") Mono<User> get(@PathVariable Long id) { return repo.findById(id); }
When WebFlux wins
- High concurrency, I/O-bound workloads: API gateways, fan-out aggregation of many downstream calls, lots of slow/idle connections.
- Streaming: server-sent events, large or infinite responses where you want backpressure and bounded memory.
- You can be reactive end-to-end (R2DBC,
WebClient, reactive security).
When MVC wins
- CPU-bound work — an event loop gives you nothing when the bottleneck is computation, not waiting.
- You depend on blocking libraries (JDBC/JPA, blocking SDKs). Wrapping them on a bounded elastic scheduler claws back blocking but adds complexity and erases most of the benefit.
- Simplicity and debuggability: imperative code, readable stack traces, mature tooling, the whole team already knows it. The reactive learning curve and "stack traces from hell" are real costs.