Careeroza
Virtual Threads
One Property
Tomcat normally uses a pool of 200 platform threads. Each request holds a thread even while it waits for the database or an HTTP call. Java 21 virtual threads are cheap (thousands of them cost a few MB), so blocking is no longer expensive. Spring Boot 3.2+ switches Tomcat, @Async and scheduling to virtual threads with one property:
Example
application.yml
spring:
threads:
virtual:
enabled: true # needs Java 21+Example
Log lines now show virtual threads:
curl -s localhost:8080/api/products/3Example output
INFO [shopease] [tomcat-handler-17] c.s.ProductController : GET /api/products/3 -- Thread.currentThread() = VirtualThread[#93,tomcat-handler-17]/runnable@ForkJoinPool-1-worker-3
10,000 Blocking Tasks
Example
Each task "waits" 100 ms (like a DB call). 10,000 virtual threads finish in a fraction of that time; a pool of 200 platform threads would need about 5 seconds:
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import java.util.stream.*;
public class Main {
public static void main(String[] args) {
AtomicInteger done = new AtomicInteger();
long start = System.nanoTime();
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10_000).forEach(i -> executor.submit(() -> {
Thread.sleep(100); // blocking I/O: the virtual thread unmounts, carrier is reused
done.incrementAndGet();
return i;
}));
} // close() waits for all tasks
long ms = (System.nanoTime() - start) / 1_000_000;
System.out.println("completed: " + done.get());
System.out.println("under 3 seconds: " + (ms < 3000));
System.out.println("200 platform threads would need about " + (10_000 / 200) * 100 + " ms");
}
}Output
completed: 10000 under 3 seconds: true 200 platform threads would need about 5000 ms
When It Helps and Watch-Outs
| Helps | Does not help |
|---|---|
| many requests waiting on DB / HTTP / files | CPU-heavy work (image resizing, PDF rendering) |
| replacing reactive code just for scalability | when the database pool is the real limit |
- The DB pool is still 20 connections: 10,000 virtual threads will queue for them. Keep
connection-timeoutshort. - Avoid long
synchronizedblocks around I/O on older JDKs (pinning; fixed in Java 24). - Do not pool virtual threads, and avoid big
ThreadLocalcaches.