Virtual threads in a Spring Boot service
When they help, when they pin, and what they do not fix.
Open this lesson in the learning hubKey points
- On Java 21,
spring.threads.virtual.enabled=truemakes Tomcat serve each request on a virtual thread. A blocking call then unmounts the carrier thread instead of holding it. - This is why it matters: the old ceiling was a few hundred platform threads, each costing about a megabyte of stack. A blocking service was limited by thread count long before it was limited by CPU.
- It gives you the scalability of reactive code without rewriting anything into a reactive chain - the programming model stays blocking and debuggable, with real stack traces.
- It does not make anything faster. One request takes exactly as long; you can simply have far more of them in flight.
- It does not remove the real bottlenecks either. A connection pool of ten still admits ten concurrent queries, and ten thousand virtual threads queuing on it just moves the wait.
- Before JDK 24, a virtual thread inside a
synchronizedblock pinned its carrier thread and could not unmount. Library code holding a monitor across I/O was the usual culprit; aReentrantLockdoes not pin.
Example
# Boot 3.2 or newer, on Java 21 or newer.
spring:
threads:
virtual:
enabled: true
# Pinning was the trap before JDK 24. Detect it rather than guessing:
# java -Djdk.tracePinnedThreads=full -jar app.jar
# Sizing still matters. Virtual threads remove the THREAD limit, not the
# resource limit - the pool below is the real concurrency ceiling for queries.
datasource:
hikari:
maximum-pool-size: 20
---
# The trade-off, stated plainly:
#
# Platform threads 200 threads x 1 MB stack -> ~200 concurrent blocking requests
# Virtual threads heap-allocated stacks -> tens of thousands, same code
#
# What does NOT change:
# - per-request latency
# - database pool size
# - downstream rate limits
# - the need for timeouts on every remote call
Virtual threads raise the concurrency ceiling of blocking code; they do not make a request faster or a connection pool bigger.
This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Spring Boot course, and every lesson in it is listed on the Spring Boot contents page.