What is the difference between the thread-per-request model of Spring MVC and the event-loop model of Spring WebFlux?
answer
- MVC: 1 thread per request, blocks while waiting
- WebFlux: few event-loop threads, ~1 per core
- Never block the event loop
- Multiplex thousands on a handful of threads
- Mono/Flux = lazy non-blocking pipeline
basics
~20 sSpring MVC gives each request its own thread that stays busy until the response is sent, so it uses many threads. WebFlux uses a few event-loop threads that handle many requests by never blocking and switching between them.
solid answer
~40 sIn the servlet (Spring MVC) model, the container assigns one thread per in-flight request. That thread is occupied for the whole request, including while it waits on a database or remote call, so concurrency is bounded by the thread-pool size (e.g., Tomcat's 200 default). WebFlux instead runs on a small fixed pool of event-loop threads — typically one per CPU core via Reactor Netty. A request never owns a thread while waiting; work is expressed as a non-blocking Mono/Flux pipeline, and when I/O would block, the thread is released to serve other requests and resumes via a callback when data is ready. This lets a handful of threads multiplex thousands of concurrent connections. The trade-off: you must never block an event-loop thread, and debugging/stack traces span callbacks rather than one linear thread.
code
java · 20 lines// Same annotation, two very different runtimes.
// Spring MVC (thread-per-request): the calling thread blocks on the JDBC call.
@RestController
class MvcController {
@GetMapping("/mvc/user/{id}")
User get(@PathVariable Long id) {
return userRepository.findById(id).orElseThrow(); // blocks this thread
}
}
// Spring WebFlux (event-loop): returns a Mono; the event-loop thread is
// released while R2DBC waits, and resumes via callback when the row arrives.
@RestController
class FluxController {
@GetMapping("/flux/user/{id}")
Mono<User> get(@PathVariable Long id) {
return userRepository.findById(id); // non-blocking, returns Mono<User>
}
}go deeper
Know the one-line contrast: MVC = one thread per request (blocks while waiting); WebFlux = a few event-loop threads that never block.
Explain the pool-size cap (Tomcat ~200) vs event-loop-per-core, and the golden rule: never block the event loop.
Discuss the multiplexing mechanism (callbacks release the thread), boundedElastic offloading, and that WebFlux scales concurrency, not raw speed.
Frame it as a scalability-vs-complexity trade-off and connect it to end-to-end reactivity (R2DBC, WebClient) being required for the model to pay off.
## The two runtime models **Thread-per-request (servlet / Spring MVC).** A servlet container (Tomcat, Jetty, Undertow) keeps a pool of worker threads. When an HTTP request arrives, the container hands it a thread from that pool and that single thread runs the entire request: controller method, service logic, JDBC call, serialization, and writing the response. Crucially, while the code waits on I/O — a SQL query, a REST call to another service — the thread is *blocked*: it sits idle but is not returned to the pool. So the number of requests you can process **concurrently** is capped by the pool size (Tomcat defaults to `server.tomcat.threads.max=200`). Under slow I/O, all 200 threads can be parked waiting, and request 201 queues even though the CPU is nearly idle. **Event-loop (reactive / Spring WebFlux).** WebFlux by default runs on **Reactor Netty**, which uses a small **fixed** pool of *event-loop* threads — by default `Runtime.getRuntime().availableProcessors()` (often 1 per core, doubled in some setups). These threads run an event loop: they pick up ready work (a new request, bytes that arrived, a completed timer) and process it in short non-blocking bursts. Your handler returns a `Mono<T>` or `Flux<T>` — a lazy description of the computation. When the pipeline hits an operation that would wait (a call over the reactive `WebClient`, an R2DBC query), it registers a callback and **returns the thread to the loop** instead of blocking. When the data is ready, some event-loop thread resumes the pipeline. Thus a few threads *multiplex* thousands of concurrent, mostly-waiting connections. ## Why the difference matters - **Memory/thread cost:** each blocked thread costs ~0.5–1 MB of stack. 10,000 concurrent slow requests in MVC would need ~10,000 threads — impractical. WebFlux handles them on a handful of threads. - **The golden rule of the event loop:** *never block an event-loop thread.* One `Thread.sleep`, one blocking JDBC call, or a synchronous `RestTemplate` on the loop stalls *every* request that thread was multiplexing. If you must call blocking code, offload it with `.subscribeOn(Schedulers.boundedElastic())`. ## Key API/class names - `Mono<T>` / `Flux<T>` — Reactor's 0..1 and 0..N publishers; the return types of WebFlux handlers. - Reactor Netty `HttpServer` — the default non-blocking server; its event-loop pool is `LoopResources`. - `Schedulers.boundedElastic()` — the escape hatch for blocking work. - `@RestController` works in *both* stacks; the difference is the return type and the underlying server. ## When to use which - **MVC / thread-per-request:** simplest mental model, mature blocking libraries (JDBC/JPA), CPU-bound or moderate-concurrency workloads. Stack traces are linear; ThreadLocal-based tooling just works. - **WebFlux / event-loop:** very high concurrency with lots of slow, I/O-bound waiting (gateways, streaming, fan-out to many services), where thread-per-request would exhaust the pool. ## Common gotcha WebFlux is *not automatically faster*. For CPU-bound work or low concurrency it offers no throughput win and costs you a harder programming model. Its advantage is **scalability under concurrency with waiting**, not raw speed.
- What happens if you call a blocking JDBC repository inside a WebFlux handler running on the event loop?That event-loop thread blocks, stalling every other request it was multiplexing; throughput collapses. Either use a reactive driver (R2DBC) or offload the blocking call with subscribeOn(Schedulers.boundedElastic()).
- Is WebFlux faster than MVC for a simple, low-traffic CRUD endpoint?No. With low concurrency and blocking data access there is no benefit and you pay a harder programming model. WebFlux wins under high concurrency with lots of I/O waiting.
saying these in an interview costs you the question
- Claiming WebFlux is 'always faster' than MVC
- Saying WebFlux uses more threads than MVC (it uses far fewer)
- Thinking each WebFlux request gets its own dedicated thread
- Believing blocking JDBC on an event-loop thread is harmless