Spring MVC vs WebFlux: Where Your Threads Actually Go
Spring MVC gives every request its own thread. That works fine with light traffic. It stops working when a thousand users hit your API at the same time.
One thread, start to finish
In Spring MVC, each HTTP request gets a thread from the servlet container's thread pool. This is the thread-per-request model.
That thread stays busy for the whole request. Controller, service, repository, database, response. It doesn't go back to the pool until the request is done.
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id); // the thread waits here until everything finishes
}More users means more threads
More concurrent requests means more threads. Each thread uses RAM. And the CPU spends time on context switching, deciding which thread runs next. Add too many threads, and the system spends more time switching between them than doing real work.
The real cost is waiting, not working
Most of a thread's time in this model isn't spent computing. It's spent waiting: for a database query, for a call to an external API.
Say a query takes 4 seconds. That thread is blocked for 4 seconds, doing nothing, just holding a slot in the pool.

WebFlux: fewer threads, none of them waiting
Spring WebFlux, built on Project Reactor and running on Netty by default, replaces thread-per-request with an event loop. A small number of threads handle everything.
When a thread needs I/O, like a database call, it sends the request and returns immediately. It doesn't wait for the answer. The OS tells the event loop when the data is ready (on Linux, this uses epoll), and the thread picks the task back up where it left off.
One thread can handle thousands of requests in flight without blocking on any single one.
@GetMapping("/users/{id}")
public Mono<User> getUser(@PathVariable Long id) {
return userService.findById(id); // returns immediately, the thread moves on to other work
}Same shape, same method name. The return type is what changes everything.
This felt familiar. If you've worked with JavaScript, in the browser or in Node, you already know this pattern: one thread, non-blocking I/O, and a callback (or a promise) that resumes when the data is ready. It's the same idea. The difference is scale. JavaScript runs on a single event loop thread. WebFlux runs several, usually one per CPU core, so it's more like a handful of small Node-style event loops working in parallel instead of just one.
Backpressure: the client sets the pace
WebFlux also changes how you stream data. Instead of sending a full response at once, the server opens a channel and pushes small chunks as they're ready.
The client can tell the server how much data it wants at a time. This is called backpressure. It's useful for anything that keeps generating data after the first response: live stock feeds, real-time location updates, that kind of stream.
The catch: everything in the chain has to be non-blocking
It only pays off if your entire stack is non-blocking. Your database driver needs to be reactive: R2DBC, not JDBC. Your HTTP client needs to be WebClient, not RestTemplate. The return type gives it away:
// JDBC: blocking, returns the value directly
public User findById(Long id) {
return jdbcTemplate.queryForObject(SQL, mapper, id);
}
// R2DBC: non-blocking, returns a reactive type
public Mono<User> findById(Long id) {
return databaseClient.sql(SQL)
.bind("id", id)
.map(mapper)
.one();
}If you use WebFlux but keep one blocking JDBC call, you block one of a handful of event loop threads, usually as many as your CPU has cores. In MVC, losing one thread out of two hundred (Spring Boot's default Tomcat pool size) barely matters. In WebFlux, losing one of a handful is a much bigger problem.
Before you migrate, check every layer for a blocking call. One is enough to erase the benefit.
Frequently asked questions
What's the main difference between Spring MVC and Spring WebFlux?
Spring MVC gives each request its own thread and blocks that thread until the request finishes. Spring WebFlux uses a small pool of threads in a non-blocking event loop, so one thread can handle many requests at once.
Is Spring WebFlux always faster than Spring MVC?
No. WebFlux helps most under high concurrency with I/O-heavy work, like slow queries or external API calls. For low traffic or CPU-heavy work, MVC often performs just as well and is simpler to reason about.
Can I use WebFlux with a normal JDBC driver?
You can, but it defeats the purpose. A blocking JDBC call inside WebFlux ties up one of the few event loop threads, which hurts more than blocking one thread in a 200-thread MVC pool. Use a reactive driver like R2DBC instead.
What is backpressure in Spring WebFlux?
Backpressure is the client's ability to tell the server how much data it can handle at a time, instead of the server pushing everything at once. It comes from the Reactive Streams spec that WebFlux implements.
Do I need to rewrite my whole app to use WebFlux?
Yes, if you want the real benefit. Controllers, services, repositories, and HTTP clients all need reactive types (Mono, Flux) end to end. Mixing blocking and reactive code in the same request path is a common and costly mistake.
Comments