Virtual threads finally landed

Java 21 Course · lesson 2 of 15 · 5 min read

What actually changed in the platform when JEP 444 went final, and why it matters at scale.

Open this lesson in the learning hub

Key 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, still ExecutorService.
  • 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.