Understanding Java Virtual Threads: Lightweight Concurrency in Java 21
Java 21 introduced one of the most significant additions to the platform in years: virtual threads, delivered as part of Project Loom. If you've ever struggled with the overhead of managing thousands of platform threads, virtual threads are about to change the way you write concurrent applications.
The Problem with Platform Threads
Traditional Java threads (now called platform threads) are thin wrappers around operating system threads. Each one consumes a significant amount of memory (typically around 1MB for the stack) and creating them is expensive. This forces developers into complex patterns like thread pools and asynchronous, callback-heavy code to achieve scalability.
java
// Traditional approach with a bounded thread pool
ExecutorService executor = Executors.newFixedThreadPool(200);
executor.submit(() -> handleRequest());
With this model, if all 200 threads are blocked on I/O, new requests must wait.
Enter Virtual Threads
Virtual threads are lightweight threads managed by the JVM rather than the OS. You can create millions of them without exhausting system resources. When a virtual thread blocks on I/O, it is unmounted from its carrier (platform) thread, freeing that carrier to run other virtual threads.
java
// Creating a virtual thread
Thread.startVirtualThread(() -> {
System.out.println("Running in a virtual thread!");
});
// Using an executor that creates a new virtual thread per task
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 1_000_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return i;
});
});
}
The example above spawns a million tasks. With platform threads this would be impossible; with virtual threads it works comfortably.
Key Benefits
- Scalability: Handle massive numbers of concurrent tasks with a simple blocking style.
- Simplicity: Write straightforward, synchronous-looking code instead of complex reactive chains.
-
Compatibility: Virtual threads implement
java.lang.Thread, so existing APIs work unchanged.
Using Virtual Threads in Spring Boot
Spring Boot 3.2+ makes enabling virtual threads trivial. Just add this to your application.properties:
properties
spring.threads.virtual.enabled=true
This configures Tomcat (or your embedded server) to handle each request on a virtual thread, dramatically improving throughput for I/O-bound web applications.
Things to Watch Out For
-
Pinning: When a virtual thread runs inside a
synchronizedblock during a blocking call, it stays pinned to its carrier thread. PreferReentrantLockfor critical sections that wrap I/O. - Thread pools are unnecessary: Don't pool virtual threads. Create a new one per task instead.
- CPU-bound work: Virtual threads shine for I/O-bound workloads, not CPU-intensive computation.
Conclusion
Virtual threads bring the simplicity of synchronous code together with the scalability previously reserved for reactive frameworks. As you migrate to Java 21 and Spring Boot 3.2+, consider adopting virtual threads to simplify your concurrent code while boosting performance. The future of Java concurrency is lightweight, and it's here today.
Top comments (0)