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
Thread Architecture Comparison
Platform Threads (1:1)
Virtual Threads (M:N)
- • 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
Memory Usage Visualization
Comparison for running 10,000 concurrent threads
Virtual threads use ~1000x less memory than platform threads
Java 21 Virtual Threads Code Examples
Basic Virtual Thread
Create a simple virtual thread using Thread.startVirtualThread()
1// Java 21 - Virtual Thread (Basic) 2Thread.startVirtualThread(() -> { 3 System.out.println("Running in virtual thread!"); 4 // Perform I/O operation 5 String result = fetchDataFromAPI(); 6 System.out.println("Result: " + result); 7});Virtual Thread Executor
Use ExecutorService with virtual threads for concurrent tasks
1// Java 21 - Virtual Thread Executor 2try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { 3 // Submit 10,000 concurrent tasks 4 IntStream.range(0, 10_000).forEach(i -> { 5 executor.submit(() -> { 6 Thread.sleep(Duration.ofSeconds(1)); 7 return fetchData(i); 8 }); 9 });10} // Executor auto-closes and waits for all tasksPlatform Thread (Old Way)
Traditional platform thread approach - limited scalability
1// Traditional Platform Threads (Pre-Java 21) 2// Limited to ~4,000 threads due to OS constraints 3ExecutorService executor = Executors.newFixedThreadPool(200); 4 5for (int i = 0; i < 10_000; i++) { 6 executor.submit(() -> { 7 // Each task blocks a precious OS thread 8 Thread.sleep(Duration.ofSeconds(1)); 9 return fetchData();10 });11}12// Many tasks waiting in queue, poor throughputStructured Concurrency
Use StructuredTaskScope for better task management (Preview)
1// Java 21 - Structured Concurrency (Preview) 2try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { 3 // Fork subtasks as virtual threads 4 Subtask<User> userTask = scope.fork(() -> fetchUser(id)); 5 Subtask<Order> orderTask = scope.fork(() -> fetchOrder(id)); 6 7 scope.join(); // Wait for all 8 scope.throwIfFailed(); // Handle errors 910 // Both completed successfully11 return new Response(userTask.get(), orderTask.get());12}HTTP Server Example
High-throughput HTTP server handling millions of connections
1// Java 21 - HTTP Server with Virtual Threads 2var server = HttpServer.create(new InetSocketAddress(8080), 0); 3server.setExecutor(Executors.newVirtualThreadPerTaskExecutor()); 4 5server.createContext("/api", exchange -> { 6 // Each request runs in its own virtual thread 7 // Can handle 1M+ concurrent connections! 8 String data = queryDatabase(); // Blocking I/O is fine 9 byte[] response = data.getBytes();10 exchange.sendResponseHeaders(200, response.length);11 exchange.getResponseBody().write(response);12});1314server.start();Key Insight
Virtual threads let you write simple, blocking code while achieving the performance of complex async/reactive patterns. No more callback hell or complex reactive streams for I/O-bound applications!