Skip to content

Expert · Multithreading and concurrency · topic 45 of 60 · 25 min

Virtual threads (Java 21)

Explanation

Virtual threads (final in Java 21) are lightweight threads managed by the JVM, not the operating system. You can run hundreds of thousands of them, each doing simple blocking code (calling a database or an HTTP API), while a few OS threads carry them. They make high-concurrency I/O easy, but they do not make CPU-heavy work faster.

The problem they solve

Traditional Java threads (now called platform threads) each map to an operating-system thread, which costs memory (often around 1 MB of stack reserved) and is expensive to create and switch. A server using one platform thread per request could handle only a few thousand requests at once, even though most of those threads spend their time just WAITING for databases and APIs. Developers worked around this with complex asynchronous (reactive) code.

A virtual thread is a thread that the JVM schedules itself. When it blocks on I/O, the JVM parks it (saves its small stack in memory) and runs another virtual thread on the same OS "carrier" thread. Blocking becomes cheap, so you can write simple, readable, blocking code and still handle huge numbers of concurrent requests.

java
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
    for (int i = 0; i < 100_000; i++) {
        int id = i;
        executor.submit(() -> {
            Thread.sleep(Duration.ofSeconds(1));    // simulated blocking I/O
            return id;
        });
    }
}   // waits for all; 100,000 sleeping tasks finish in about 1 second
java
Thread vt = Thread.ofVirtual().name("order-", 0).start(() -> processOrder());
Thread.startVirtualThread(() -> sendSms());
System.out.println(vt.isVirtual());   // true
Other ways to start a virtual thread.

When they help, and when they do not

WorkloadVirtual threads?Why
Many requests that wait on DB / HTTP / filesYesWaiting is cheap; throughput rises
CPU-heavy work (image processing, big calculations)No benefitStill limited by the number of cores
Small number of long-running background tasksNot neededPlatform threads are fine

Guidelines

  • Do not pool virtual threads; create one per task (newVirtualThreadPerTaskExecutor). They are cheap.
  • Limit access to scarce resources (database connections, a partner API's rate limit) with a Semaphore or the connection pool, not by limiting threads.
  • ThreadLocal works but becomes costly with millions of threads; avoid storing big objects in it.
  • Frameworks support them: for example Spring Boot (3.2+) can run request handling on virtual threads with the property spring.threads.virtual.enabled=true.

Pinning (in brief)

In Java 21, a virtual thread that blocks while inside a synchronized block is "pinned" to its carrier OS thread, which reduces the benefit. Java 24 (JEP 491) removed most of this limitation for synchronized. On Java 21, prefer ReentrantLock around blocking I/O in hot paths; on newer JDKs this matters much less.

Careful: Virtual threads are not a magic speed-up. If a service is slow because each request does heavy computation, or because the database is overloaded, adding virtual threads changes nothing (or makes the database overload worse). Measure first.
Think of it like this: Platform threads are like booking a full taxi for every passenger, even while they sit in a shop for an hour. Virtual threads are like a shared-auto system: when a passenger stops to shop, they step out and the auto takes someone else, and when they are ready, any free auto picks them up again.
Tip: Read JEP 444 once. It explains the design in clear language and is the source interviewers expect you to know about.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Virtual threads (Java 21) · 1/6

Virtual threads

In this lesson you will meet virtual threads, added in Java 21.

Prefer a video by a teacher? Find free videos on “java 21 virtual threads explained jep 444” on YouTube ↗

In real life: A notification service handling a sale-day spike

On a big sale day, an e-commerce company's notification service must call an SMS gateway and an email API for hundreds of thousands of customers, each call waiting a few hundred milliseconds. Switching the request handling to virtual threads let the same servers keep tens of thousands of calls in flight with simple blocking code, while a Semaphore kept the SMS gateway within its allowed rate.

java
Semaphore smsPermits = new Semaphore(200);   // gateway allows 200 concurrent calls
try (var exec = Executors.newVirtualThreadPerTaskExecutor()) {
    for (Customer c : customers) {
        exec.submit(() -> {
            smsPermits.acquire();
            try { smsGateway.send(c.phone(), "Sale is live!"); }
            finally { smsPermits.release(); }
            return null;
        });
    }
}

Practice (0/3 done)

Task 1 · Question

Which workload benefits MOST from virtual threads?

Task 2 · Question

Should you put virtual threads in a fixed-size pool?

Task 3 · Practice

On Java 21+, write a program that submits 10,000 tasks that each sleep 1 second, first with Executors.newFixedThreadPool(100) and then with Executors.newVirtualThreadPerTaskExecutor(), both in try-with-resources. Measure and compare the total time of each (about 100 s vs about 1 s).

Quick quiz

Question 1

1.In which Java version did virtual threads become a final (non-preview) feature?

Question 2

2.What happens when a virtual thread blocks on I/O?

Question 3

3.Do virtual threads make CPU-bound computations faster?

Question 4

4.What is "pinning"?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

In which Java version did virtual threads become a final (non-preview) feature?

Answer: Java 21

JEP 444, Java 21.

What happens when a virtual thread blocks on I/O?

Answer: The JVM parks it and runs another virtual thread on the carrier

That is what makes blocking cheap.

Do virtual threads make CPU-bound computations faster?

Answer: No, the number of cores is still the limit

They improve throughput for waiting, not raw computation.

What is "pinning"?

Answer: A virtual thread stuck to its carrier while blocking, e.g. inside synchronized on Java 21

Java 24 removed most pinning caused by synchronized.

Does a daemon thread keep the JVM running?Related

Answer: No

The JVM exits when only daemon threads remain.

From Threads: doing things at the same time

What runs Java bytecode?Related

Answer: The Java Virtual Machine (JVM)

Bytecode runs on the JVM.

From What Java is and where it is used

What happens to running threads when main returns?Related

Answer: The program exits and they are stopped

Join threads whose work must complete.

From Threads

What kind of work benefits most from async/await?Related

Answer: Waiting for network, disk or database (I/O)

async frees threads during I/O waits.

From async and await: waiting without blocking

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. This is the last topic of “Multithreading and concurrency”. The module exam comes next.
  2. Module 9 exam: Multithreading and concurrency · 12 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, JDBC, Maven, Gradle and JUnit. 60 topics left in the course before the final exam.

← Previous: Atomic variables and concurrent collections