Virtual threads finally landed
What actually changed in the platform when JEP 444 went final, and why it matters at scale.
Open this lesson in the learning hubKey points
- Before 21 a Java thread was an OS thread: about 1 MB of stack, and a few thousand was the ceiling.
- A virtual thread is scheduled by the JVM onto a small pool of carrier threads and unmounts while blocked.
- So blocking code scales - you no longer rewrite everything into callbacks just to survive load.
- The API is deliberately unchanged: it is still
Thread, stillExecutorService. - For how they work internally see Multithreading; this lesson is about the platform shift.
Example
import java.time.Duration;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class Main {
public static void main(String[] args) throws Exception {
AtomicInteger done = new AtomicInteger();
long start = System.currentTimeMillis();
try (ExecutorService vt = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 10_000; i++) {
vt.submit(() -> {
Thread.sleep(Duration.ofMillis(50)); // blocking, on purpose
done.incrementAndGet();
return null;
});
}
} // close() waits for every task
System.out.println("tasks completed : " + done.get());
System.out.println("elapsed ms : " + (System.currentTimeMillis() - start));
System.out.println("10,000 platform threads would need ~10 GB of stack");
}
}
Virtual threads did not make code faster - they made blocking code stop being expensive.
This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Java 21 Course course, and every lesson in it is listed on the Java 21 Course contents page.