DEV Community

Cover image for What Actually Happens When the 201st User Hits Your Spring Boot App
Nikhil Kamani
Nikhil Kamani

Posted on

What Actually Happens When the 201st User Hits Your Spring Boot App

The setup
Your Spring Boot app runs on embedded Tomcat. By default, Tomcat has a pool of about 200 threads. Every incoming HTTP request grabs one thread from that pool, holds it for the entire duration of the request, and returns it when the response is sent.

Your typical controller does something like this:

@GetMapping("/order/{id}")
public OrderView getOrder(@PathVariable String id) {
    var order    = orderClient.fetch(id);      // REST call — ~200ms waiting
    var payment  = paymentClient.status(id);   // REST call — ~200ms waiting
    var shipping = shippingClient.track(id);   // REST call — ~200ms waiting
    return combine(order, payment, shipping);
}
Enter fullscreen mode Exit fullscreen mode

Three sequential network calls. About 600ms per request — and here's the key part: for almost all of those 600ms, the thread is doing nothing. It's just parked, waiting for other servers to reply.

The 201st user
200 users hit this endpoint at the same time. All 200 Tomcat threads are now holding a request, each one blocked, each one waiting on a slow downstream call.

The 201st user arrives.

There is no thread left to give them. Their request doesn't fail — it queues. It sits and waits for one of the 200 threads to finish and free up. Your server's CPU is nearly idle (everyone's just waiting on I/O), your memory is fine — and yet users are stuck in a line. This is thread pool exhaustion, and it's one of the most common ways a healthy-looking service falls over under load.

For years we solved this two painful ways: crank the pool size way up (each platform thread costs ~1MB of memory, so this doesn't scale far), or rewrite everything in reactive/WebFlux style (fast, but the code gets much harder to read and debug).

What virtual threads change

A platform thread is a thin wrapper around a real OS thread — expensive, ~1MB, you can have a few thousand. A virtual thread is managed by the JVM instead of the OS. It costs a few hundred bytes, and you can have millions of them.

The magic is what happens when a virtual thread blocks on I/O. Instead of sitting there holding an OS thread hostage, the JVM unmounts it — the virtual thread steps aside, the real OS thread is freed to go run someone else's work, and when the network reply finally comes back, the virtual thread gets re-mounted onto any free OS thread to continue.

So during those three REST calls, the underlying OS thread isn't wasted waiting. It's off serving other requests.

In Spring Boot 3.2+, you turn this on with a single line:

spring.threads.virtual.enabled=true

Now every request runs on its own fresh virtual thread. There is no pool of 200 to exhaust. The 201st user — and the 20,000th — each get their own virtual thread, and the handful of real OS threads underneath stay busy instead of blocked. Same simple blocking code above. No WebFlux. No callback spaghetti.

The catch (this is the real interview follow-up)

Virtual threads only help when threads spend their time waiting — I/O-bound work. For CPU-bound work (heavy computation), there's nothing to unmount from, so they give no benefit over a normal pool sized to your cores.

And one trap that bites people: if a virtual thread blocks inside a synchronised block, the JVM currently can't unmount it — it stays "pinned" to its OS thread for the whole block, and you lose the entire advantage. In code that runs on virtual threads, prefer ReentrantLock over synchronised for anything that wraps a blocking call.

That's the full answer to "what happens to the 201st user" — the pool, the exhaustion, the unmount/re-mount, and the one line that changes it. If you can tell that story in an interview, you're already ahead of most candidates.

I cover this and more (Core Java, Java 8 to 21, Multithreading, Spring Boot, Microservices, Design Patterns, Coding Round Patterns) in my guide.

Full guide: https://kamaninikhil.gumroad.com/l/java-interview-guide

Feedback or questions? Email me: kamaninikhil71@gmail.com

Ever been burned by thread pool exhaustion in production? What did you do before virtual threads existed?

Top comments (0)