Understanding Project Loom’s Virtual Threads
Project Loom is an ongoing effort by the OpenJDK community to introduce lightweight, efficient, and scalable concurrency constructs into the Java platform. It primarily focuses on virtual threads, structured concurrency, and other improvements to simplify concurrent programming.
Key Features of Project Loom
- Virtual Threads – Lightweight, user-mode threads that allow high concurrency with low resource usage.
- Structured Concurrency – A new programming model that makes concurrent code easier to read and reason about.
- Scoped Values – A mechanism for managing context-specific data more efficiently than
ThreadLocal.
1. Virtual Threads
What Are Virtual Threads?
- Unlike traditional platform threads (OS threads), virtual threads are lightweight threads managed by the JVM.
- They are not tied to OS threads; instead, many virtual threads can be multiplexed onto a smaller number of OS threads.
- Java developers can create millions of virtual threads, reducing thread contention and memory usage.
How Virtual Threads Work
- Virtual threads do not block OS threads when performing blocking operations.
- Instead of being blocked, a virtual thread performing I/O or a sleep operation yields control, allowing other virtual threads to run.
- This is enabled by the ForkJoinPool and an optimized thread scheduling model in Java.
Benefits of Virtual Threads
- Highly Scalable: Supports thousands to millions of threads with minimal memory overhead.
- Simplifies Asynchronous Code: Eliminates the need for complex reactive programming (e.g.,
CompletableFuture, RxJava). - Better Resource Utilization: Since virtual threads are cheaper, fewer OS threads are needed.
How to Use Virtual Threads
Java 21 introduces virtual threads in the java.lang.Thread API.
Creating Virtual Threads
javaCopyEditThread.startVirtualThread(() -> {
System.out.println("Hello from a virtual thread!");
});
Using an Executor with Virtual Threads
javaCopyEditExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
IntStream.range(0, 10).forEach(i ->
executor.submit(() -> {
System.out.println("Task " + i + " executed by " + Thread.currentThread());
})
);
executor.shutdown();
💡 Each task runs in its own virtual thread!
2. Structured Concurrency
What Is Structured Concurrency?
Structured concurrency aims to manage multiple concurrent tasks as a single unit, ensuring better error handling and readability.
Traditional Approach (Fork-Join)
Without structured concurrency, developers often create multiple threads and manually manage them:
javaCopyEditExecutorService executor = Executors.newFixedThreadPool(2);
Future<String> result1 = executor.submit(() -> fetchDataFromAPI());
Future<String> result2 = executor.submit(() -> fetchDataFromDB());
- This approach makes error handling difficult.
- The lifecycle of these tasks is not structured—you must manually track and manage them.
Structured Concurrency API
With structured concurrency, Java manages all child tasks as part of a single parent structure.
Using StructuredTaskScope (Java 21)
javaCopyEdittry (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<String> result1 = scope.fork(() -> fetchDataFromAPI());
Future<String> result2 = scope.fork(() -> fetchDataFromDB());
scope.join(); // Wait for all tasks to complete
scope.throwIfFailed(); // Propagate exceptions
System.out.println("API: " + result1.resultNow());
System.out.println("DB: " + result2.resultNow());
}
Benefits of Structured Concurrency
- Easier Cancellation & Propagation: If one task fails, all related tasks can be canceled.
- Better Exception Handling: You don’t have to manually catch exceptions from multiple futures.
- Improved Readability: The structure mirrors sequential code, making it intuitive.
3. Scoped Values
What Are Scoped Values?
Scoped values are an alternative to ThreadLocal for managing thread-specific data. They reduce memory footprint and allow efficient access in concurrent environments.
How Scoped Values Work
- Scoped values are immutable, making them safe to use in concurrent contexts.
- Unlike
ThreadLocal, scoped values do not require cleanup.
Example Using Scoped Values
javaCopyEditScopedValue<String> USER_ID = ScopedValue.newInstance();
void processUserRequest() {
ScopedValue.where(USER_ID, "1234").run(() -> {
System.out.println("User ID: " + USER_ID.get()); // Prints: 1234
});
}
Comparison: ThreadLocal vs Scoped Values
| Feature | ThreadLocal | Scoped Values |
|---|---|---|
| Memory Usage | High (each thread stores its own copy) | Low (shared, immutable) |
| Garbage Collection | Requires explicit cleanup | No explicit cleanup needed |
| Performance | Slower for high-concurrency workloads | Faster due to immutability |
Project Loom vs Traditional Concurrency Models
| Feature | Traditional Threads | Virtual Threads |
|---|---|---|
| Memory Usage | High (~1MB per thread) | Low (~2KB per thread) |
| Thread Creation Time | Slow | Fast |
| Scalability | Limited (~10,000 threads) | Millions of threads |
| Blocking Calls | Waste OS thread resources | Yield to scheduler |
| Complexity | Requires pools (ExecutorService) | Simplifies concurrency |
Impact of Project Loom
- Microservices & Web Servers
- Reduces the need for complex asynchronous frameworks like RxJava, Reactor, or CompletableFuture.
- Tomcat, Netty, and Jetty can handle many more requests without blocking OS threads.
- Database & I/O Operations
- JDBC and file operations become more efficient since blocking operations no longer waste OS threads.
- Game & High-Concurrency Applications
- Games, simulations, and chat applications can create millions of threads without performance degradation.
Comparison with Other Concurrency Models
| Feature | Virtual Threads (Loom) | Kotlin Coroutines | Go Routines |
|---|---|---|---|
| Threading Model | User-mode threads | Continuation-based | Green threads |
| Ease of Use | Similar to Java threads | Requires learning coroutine APIs | Simple |
| Performance | High | High | Very high |
| Interoperability | Works with existing Java APIs | Requires coroutine-specific libraries | Native support |
Frequently Asked Questions (FAQ)
1. Do Virtual Threads Replace ExecutorService?
No, but they reduce the need for traditional ThreadPoolExecutor. You can still use Executors.newVirtualThreadPerTaskExecutor().
2. Can Virtual Threads Be Used with Traditional Blocking Code?
Yes! They work seamlessly with blocking I/O, JDBC, HTTP clients, and file operations.
3. Do Virtual Threads Use Thread Pools?
No, each virtual thread is independent. However, they share a small number of OS threads, managed by the JVM.
4. Are Virtual Threads a Replacement for Reactive Programming?
In many cases, yes. Virtual threads make async programming simpler without requiring RxJava, Reactor, or CompletableFuture.
5. When Will Project Loom Be Fully Integrated into Java?
- Java 19 (Preview: Virtual Threads & Structured Concurrency)
- Java 20 (Second Preview)
- Java 21 (Stable release with Virtual Threads & Structured Concurrency)


