Virtual Threads
1. What are Virtual Threads?
Virtual Threads = lightweight threads managed by JVM (not OS)
- Introduced via Project Loom
- Officially final in Java 21
- Designed to handle millions of concurrent tasks efficiently
Unlike traditional threads:
- They are not 1:1 mapped to OS threads
- They are scheduled by JVM, not the OS
Key Characteristics
2. How Virtual Threads Work Internally
Platform Threads (Before)
1 Java Thread → 1 OS ThreadVirtual Threads (Now)
Many Virtual Threads → Few OS Threads (Carrier Threads)Concept: Mount / Unmount
- When running → virtual thread is mounted on OS thread
- When blocked (e.g., I/O) → it is unmounted
- OS thread becomes free to handle another virtual thread
This is the game changer
3. Simple Example (Java 21)
public class VirtualThreadDemo {
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> {
System.out.println("Running in: " + Thread.currentThread());
};
// Create virtual thread
Thread vt = Thread.ofVirtual().start(task);
vt.join();
}
}Creating Many Threads
public class VirtualScale {
public static void main(String[] args) {
for (int i = 0; i < 1_000_000; i++) {
Thread.startVirtualThread(() -> {
// simulate work
});
}
}
}This is practical with virtual threads
Impossible with platform threads
4. Executor-Based Usage (Real-world)
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10000)
.forEach(i -> executor.submit(() -> {
// blocking operations (DB, API calls)
Thread.sleep(100);
return i;
}));
}Best use case: I/O-heavy applications
5. When to Use Virtual Threads
Perfect for:
- REST APIs
- Database calls
- Microservices
- Blocking I/O operations
- High concurrency workloads
Not ideal for:
- CPU-intensive tasks
- Long-running compute-heavy jobs
6. Before Virtual Threads (Traditional Approach)
1. Thread Per Request Model (Old Way)
new Thread(() -> handleRequest()).start();Problems:
- High memory usage (~1MB per thread)
- Limited scalability (~thousands max)
- Context-switch overhead
- Thread exhaustion
2. Thread Pools (Improved Old Approach)
ExecutorService pool = Executors.newFixedThreadPool(100);
pool.submit(() -> {
// work
});Why Thread Pools?
Reuse threads
Avoid creation cost
Control concurrency
Limitations:
3. Asynchronous Programming (Reactive / CompletableFuture)
To avoid blocking:
CompletableFuture.supplyAsync(() -> fetchData())
.thenApply(data -> process(data))
.thenAccept(result -> save(result));Reactive Style (WebFlux, etc.)
Mono.fromCallable(() -> fetchData())
.map(this::process)
.subscribe();Problems with Async Approach
7. Virtual Threads vs Old Approaches
Comparison Table
8. Biggest Advantage
Virtual threads let you:
Get Syed Nazeer’s stories in your inbox
Join Medium for free to get updates from this writer.
Write simple blocking code
With non-blocking scalability
Old Style (Complex Async)
CompletableFuture.supplyAsync(() -> dbCall())
.thenApply(data -> apiCall(data))
.thenAccept(System.out::println);Virtual Thread Style (Simple)
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> {
String data = dbCall(); // blocking
String result = apiCall(data); // blocking
System.out.println(result);
});
}Same scalability, much cleaner code
9. Important Concepts
1. Carrier Threads
- Backed by OS threads (ForkJoinPool)
- Execute virtual threads
2. Pinning (Important for Interviews)
A virtual thread gets pinned when:
- Inside
synchronized - Native calls
- Blocking operations not Loom-aware
Problem:
- Prevents unmounting → hurts scalability
Example of Pinning
synchronized (this) {
Thread.sleep(1000); // blocks carrier thread
}Use:
ReentrantLockinstead ofsynchronized
3. Structured Concurrency (Java 21 Preview)
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<String> f1 = scope.fork(() -> service1());
Future<String> f2 = scope.fork(() -> service2());
scope.join();
return f1.resultNow() + f2.resultNow();
}Cleaner parallel code
10. Real-world Example (Spring Boot)
Works automatically if enabled:
spring.threads.virtual.enabled=trueEach request handled by virtual thread
No need for reactive frameworks
11. Migration Strategy
Replace:
- Thread pools (I/O heavy code)
- CompletableFuture chains (optional)
- Reactive frameworks (in some cases)
Keep:
- Async for CPU-heavy tasks
- Parallel streams
12. Summary (Interview Style)
Virtual Threads:
- Lightweight, JVM-managed threads
- Enable millions of concurrent tasks
- Best for I/O-bound workloads
Before:
- Platform threads → expensive
- Thread pools → limited scalability
- Reactive → complex
Now: Simple blocking code + Massive scalability
Final One-Liner (Interview)
“Virtual threads in Java 21 decouple Java threads from OS threads, allowing millions of lightweight concurrent tasks while keeping a simple blocking programming model — eliminating the need for complex asynchronous code.”






