Java 21 Feature

Virtual Threads

Lightweight threads that dramatically reduce the effort of writing, maintaining, and observing high-throughput concurrent applications.

1M+Concurrent Threads
~1KBMemory per Thread
1000xLess Memory

What are Virtual Threads?

Virtual threads are lightweight threads introduced in Java 21 (JEP 444) that enable high-throughput concurrent applications. They are managed by the JVM rather than the operating system, allowing you to create millions of threads efficiently.

Massive Scalability

Create millions of virtual threads instead of being limited to thousands of platform threads. Perfect for I/O-bound workloads.

Low Memory Footprint

Each virtual thread uses only ~1KB of memory compared to ~1MB for platform threads. Run more concurrent tasks with less RAM.

Simple Programming Model

Write straightforward blocking code without complex async patterns. The JVM handles the complexity of efficient scheduling.

Seamless Integration

Virtual threads work with existing Java code. Use familiar APIs like ExecutorService, locks, and blocking I/O operations.

Fast Context Switching

Context switching happens in user space, not kernel space. Much faster than OS-level thread switching.

Automatic Carrier Management

Virtual threads automatically mount and unmount from carrier threads. When blocked on I/O, the carrier is freed for other work.

When to Use Virtual Threads

Ideal Use Cases

  • Web servers handling many concurrent requests
  • Database-heavy applications with connection pools
  • Microservices making multiple API calls
  • File processing and batch operations

Not Recommended For

  • CPU-intensive computations (use platform threads)
  • Long-running synchronized blocks
  • Native code that blocks in JNI
  • Code using ThreadLocal for pooled resources

Evolution of Java Concurrency

Java 1.0 (1996)
Basic Thread class introduced
Java 5 (2004)
java.util.concurrent package, ExecutorService
Java 7 (2011)
Fork/Join framework for parallel computation
Java 8 (2014)
CompletableFuture, parallel streams
Java 19 (2022)
Virtual Threads (Preview)
Java 21 (2023)
Virtual Threads (GA) - JEP 444

Thread Architecture Comparison

Platform Threads (1:1)

Java Threads
T0
T1
T2
T3
1:1
1:1
1:1
1:1
OS Threads
OS0
OS1
OS2
OS3
T0 running...

Virtual Threads (M:N)

Virtual Threads
V0
V1
V2
V3
V4
V5
+ millions more...
mounted on
Carrier Threads
C02 VTs
C11 VTs
OS Threads
OS0
OS1
VTs swap in/out dynamically
  • • Each Java thread = one OS thread
  • • ~1MB memory per thread
  • • Blocked thread = wasted OS thread
  • • Limited to ~4,000 threads
  • • Expensive context switching
  • • Many virtual threads share few carriers
  • • ~1KB memory per thread
  • • Blocked = unmount, carrier freed
  • • Millions of threads possible
  • • Fast user-space scheduling

Platform vs Virtual Threads Comparison

Metric
Platform Threads
Virtual Threads
Memory per Thread
1MB
0.001MB
Max Concurrent Threads
4,000
1,000,000
Thread Creation Time
1,000μs
1μs
Context Switch Cost
High
Low
OS Thread Mapping
1:1
M:N
Blocking I/O Impact
Thread Blocked
Carrier Released

Memory Usage Visualization

Comparison for running 10,000 concurrent threads

Platform Threads~10 GB RAM
Virtual Threads~10 MB RAM

Virtual threads use ~1000x less memory than platform threads

Before vs After Java 21

Creating a Thread

Simple thread creation for a background task

Platform Thread
// Pre-Java 21: Platform Thread
Thread thread = new Thread(() -> {
System.out.println("Running!");
String result = fetchDataFromAPI();
System.out.println(result);
});
thread.start();
// Each thread consumes ~1MB of memory
// Limited to ~4,000 concurrent threads
Virtual Thread (Java 21)
// Java 21: Virtual Thread
Thread.startVirtualThread(() -> {
System.out.println("Running!");
String result = fetchDataFromAPI();
System.out.println(result);
});
// Each thread consumes ~1KB of memory
// Can create millions of threads

Executor Service

Running 10,000 concurrent tasks

Fixed Thread Pool
// Pre-Java 21: Fixed Thread Pool
ExecutorService executor =
Executors.newFixedThreadPool(200);
for (int i = 0; i < 10_000; i++) {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return fetchData();
});
}
// Only 200 tasks run concurrently
// 9,800 tasks waiting in queue
// Total time: ~50 seconds
Virtual Thread Executor (Java 21)
// Java 21: Virtual Thread Per Task
try (var executor = Executors
.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 10_000; i++) {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return fetchData();
});
}
}
// All 10,000 tasks run concurrently
// Total time: ~1 second

HTTP Server

Handling concurrent HTTP requests

Thread Pool Server
// Pre-Java 21: Limited concurrency
var server = HttpServer.create(
new InetSocketAddress(8080), 0);
server.setExecutor(
Executors.newFixedThreadPool(200));
server.createContext("/api", exchange -> {
// Blocked thread = wasted resource
String data = queryDatabase();
respond(exchange, data);
});
// Max ~200 concurrent requests
Virtual Thread Server (Java 21)
// Java 21: Massive concurrency
var server = HttpServer.create(
new InetSocketAddress(8080), 0);
server.setExecutor(Executors
.newVirtualThreadPerTaskExecutor());
server.createContext("/api", exchange -> {
// Blocked? No problem!
String data = queryDatabase();
respond(exchange, data);
});
// Handles 1M+ concurrent requests

Parallel API Calls

Fetching data from multiple services

CompletableFuture
// Pre-Java 21: Complex async code
CompletableFuture<User> userFuture =
CompletableFuture.supplyAsync(
() -> fetchUser(id));
CompletableFuture<Order> orderFuture =
CompletableFuture.supplyAsync(
() -> fetchOrder(id));
CompletableFuture.allOf(userFuture, orderFuture)
.thenAccept(v -> {
User user = userFuture.join();
Order order = orderFuture.join();
process(user, order);
});
// Callback complexity, error handling is hard
Structured Concurrency (Java 21)
// Java 21: Simple, readable code
try (var scope = new StructuredTaskScope
.ShutdownOnFailure()) {
var user = scope.fork(() -> fetchUser(id));
var order = scope.fork(() -> fetchOrder(id));
scope.join();
scope.throwIfFailed();
process(user.get(), order.get());
}
// Clean, linear flow, easy error handling
!

Key Insight

Virtual threads let you write simple, blocking code while achieving the performance of complex async/reactive patterns. Same code style, 1000x more throughput.

Interactive Thread Simulation

50

Platform Threads

0/50 completed
Running
Blocked (I/O)
Waiting
Completed

Virtual Threads

0/50 completed
Running
Blocked (I/O)
Waiting
Completed