Project Loom virtual threads replace platform threads. 10,000 concurrent requests without thread pool. Production ready Java 21+.
JOptimize Team
Virtual threads change everything about Java concurrency.
Traditional Java: 1 request = 1 platform thread. 10,000 requests = 10,000 threads. Each thread = 1MB stack = 10GB memory.
Virtual threads: 1 request = 1 virtual thread. 10,000 requests = 1 thread pool of 100 platform threads. Each virtual thread = few KB = 50MB total.
Traditional servlet:
@GetMapping("/api/user/{id}") public User getUser(@PathVariable String id) { User user = userService.getUser(id); // Blocks thread return user; }
At 10,000 concurrent requests:
Exact same code works differently:
@GetMapping("/api/user/{id}") public User getUser(@PathVariable String id) { User user = userService.getUser(id); // Blocks virtual thread, not platform thread return user; }
When code hits I/O (database, HTTP call, sleep), virtual thread is suspended.
Platform thread runs other virtual threads.
No explicit async needed. Code is simple and readable.
JOptimize detects this automatically
Scan your Java project in 30 seconds — free, no credit card.
10,000 requests
↓
10,000 platform threads (each 1MB stack)
↓
Context switch every 10ms
↓
GC pressure
↓
CPU cache misses
↓
→ Throughput: 100-1000 req/sec
10,000 requests
↓
10,000 virtual threads (each 1-10KB)
↓
100 platform threads (actual workers)
↓
Virtual thread blocks → park in heap
↓
Platform thread runs next virtual thread
↓
When I/O completes → resume virtual thread
↓
→ Throughput: 10,000+ req/sec (100x improvement)
Concurrent connections: 10,000 Memory used: 10GB+ Context switches/sec: 100,000+ Throughput: 500 req/sec P99 latency: 5000ms (5 seconds)
Concurrent connections: 10,000 Memory used: 50MB Context switches/sec: Minimal (no OS switching) Throughput: 50,000 req/sec (100x) P99 latency: 50ms (0.05 seconds)
# application.properties spring.threads.virtual.enabled=true
That's it. Spring boot automatically uses virtual threads.
@Configuration public class VirtualThreadConfig { @Bean public ExecutorService virtualThreadExecutor() { return Executors.newVirtualThreadPerTaskExecutor(); } }
// Still uses platform threads ExecutorService pool = Executors.newFixedThreadPool(10);
Before (Platform Threads):
@Component public class EventConsumer { @Autowired private ExecutorService executor; // 50 threads @KafkaListener(topics = "events", concurrency = "50") public void handle(String message) { executor.submit(() -> { // Process message // 50 threads, so max 50 concurrent }); } }
Result: 50 threads, 50MB stack = limited concurrency
After (Virtual Threads):
@Component public class EventConsumer { @Autowired private ExecutorService executor; @KafkaListener(topics = "events") public void handle(String message) { // Virtual thread per message // No thread pool needed process(message); } }
Result: 10,000 concurrent messages, 50MB total memory
// Bad: ThreadLocal doesn't work well across virtual threads private static final ThreadLocal<User> currentUser = new ThreadLocal<>(); // Good: Use ScopedValue (Java 21+) private static final ScopedValue<User> currentUser = ScopedValue.newInstance();
ThreadLocal was tied to OS threads. Virtual threads need ScopedValue.
// Bad: Blocks platform thread (pinning) synchronized (lock) { // Virtual thread blocks, but so does platform thread // Defeats the purpose } // Good: Use ReentrantLock lock.lock(); try { // Virtual thread blocks, platform thread continues } finally { lock.unlock(); }
Synchronized blocks cause pinning - virtual thread pins platform thread. Avoid.
Older libraries may assume 1 thread per request. They work but don't get full benefits.
Modern frameworks (Spring 6.1+, Quarkus, etc.) optimize for virtual threads.
spring.threads.virtual.enabled=trueVirtual threads are production-ready in Java 21+.
Same code, 100x throughput. No async/await plumbing.
ChangeP99 latency from seconds to milliseconds.
Smallest change for biggest impact in Java history.
Optimize with JOptimize PRO. Use code LINKEDIN40 for 40% OFF.
JOptimize finds N+1 queries, EAGER collections, and 70+ other issues in your Java codebase — in under 30 seconds.