What are virtual threads in Java 21, and how are they different from platform threads?

Virtual threads (Java 21, Project Loom) are lightweight threads managed by the JVM rather than the operating system. You can create millions of them, and when one blocks on I/O, the JVM parks it and frees the underlying OS thread for other work. This keeps the simple thread-per-request style while scaling like asynchronous code.

Key Points: • Platform threads map one-to-one to OS threads. Each reserves about 1 MB of stack, so thousands of them are expensive. • Virtual threads run on a small pool of carrier (platform) threads. Blocking calls such as JDBC, HTTP or Thread.sleep() unmount them instead of blocking the carrier. • They help I/O-bound workloads (web requests, database calls), not CPU-bound work. • Don't pool virtual threads. Create one per task, for example with Executors.newVirtualThreadPerTaskExecutor(). • In Spring Boot 3.2+, setting spring.threads.virtual.enabled=true makes Tomcat and @Async use virtual threads.

Code Example:

import java.time.Duration;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;

public class VirtualThreadsDemo {

    public static void main(String[] args) {
        AtomicInteger done = new AtomicInteger();
        long start = System.currentTimeMillis();

        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            IntStream.range(0, 10_000).forEach(i -> executor.submit(() -> {
                Thread.sleep(Duration.ofMillis(500));   // simulated I/O call
                done.incrementAndGet();
                return i;
            }));
        }                                                // close() waits for all tasks

        long seconds = Math.round((System.currentTimeMillis() - start) / 1000.0);
        System.out.println("Tasks completed: " + done.get());
        System.out.println("Took about " + seconds + " second(s), not 10,000 x 0.5 seconds");
        System.out.println("Is virtual: " + Thread.ofVirtual().unstarted(() -> {}).isVirtual());
    }
}

Output:

Tasks completed: 10000 Took about 1 second(s), not 10,000 x 0.5 seconds Is virtual: true

Good to Know: Make the point that virtual threads make blocking cheap, not fast. Each request still takes as long as its database call; the server can just handle far more of them at the same time. Then mention when not to use them, which is the natural follow-up question.

Interview Tip: A concise interview answer is:

"Virtual threads are lightweight threads managed by the JVM instead of the operating system, so I can run millions of them. When one blocks on I/O, it's unmounted from its carrier thread, which lets simple blocking code scale like asynchronous code. They help I/O-bound work, not CPU-bound work."

Previous question: What are helpful NullPointerExceptions in Java 14+?

Next question: When should you not use virtual threads, and what is thread pinning?

More Java 17 and 21 Features interview questions

All Java 17 and 21 Features questions