Understanding Java's Virtual Threads: Lightweight Concurrency in Action
Understanding Java's Virtual Threads: Lightweight Concurrency in Action Java 21 introduced virtual threads as a stable feature (JEP 444), fundamentally changing how we approach concurrency on the JVM. In this post, we'll explore what virtual threads are, why they matter, and how to use them effectively. The Problem with Platform Threads Traditional Java threads—now called platform threads —are thin wrappers around operating system threads. Each one consumes roughly 1MB of stack memory and involves the OS scheduler for context switching. This makes them expensive: java // Creating thousands of platform threads is costly for (int i = 0; i < 10_000; i++) { new Thread(() -> { // blocking I/O ties up an OS thread processRequest(); }).start(); } In high-throughput server applications, the classic "thread-per-request" model hits a ceiling because you simply cannot create enough OS threads. Enter Virtual Threads Virtual threads are managed by the JVM rather than the OS. Many virtual threads run on a small pool of carrier platform threads. When a virtual thread blocks (e.g., on I/O), the JVM detaches it from its carrier, freeing that carrier to run other virtual threads. java // Creating a million virtual threads is perfectly fine try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { IntStream.range(0, 1_000_000).forEach(i -> { executor.submit(() -> { Thread.sleep(Duration.ofSeconds(1)); return i; }); }); } Key Benefits Cheap creation : Virtual threads start with a tiny stack that grows on demand. Familiar model : You write straightforward blocking code—no callbacks or reactive chains. Better scalability : Throughput is limited by resources, not thread count. Using Virtual Threads in Spring Boot As of Spring Boot 3.2, enabling virtual threads is a one-line configuration change: properties spring.threads.virtual.enabled=true This makes Tomcat handle each request on a virtual thread, allowing your application to serve many concurrent blocking requests without exha