Expert · Multithreading and concurrency · topic 45 of 60 · 25 min
Virtual threads (Java 21)
Explanation
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.
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 secondThread vt = Thread.ofVirtual().name("order-", 0).start(() -> processOrder());
Thread.startVirtualThread(() -> sendSms());
System.out.println(vt.isVirtual()); // trueWhen they help, and when they do not
| Workload | Virtual threads? | Why |
|---|---|---|
| Many requests that wait on DB / HTTP / files | Yes | Waiting is cheap; throughput rises |
| CPU-heavy work (image processing, big calculations) | No benefit | Still limited by the number of cores |
| Small number of long-running background tasks | Not needed | Platform 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.
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
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.
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)
Which workload benefits MOST from virtual threads?
Should you put virtual threads in a fixed-size pool?
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
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Executors, futures and CompletableFutureMultithreading and concurrency · same module
- Race conditions and synchronizedMultithreading and concurrency · same module
- ThreadsRust Programming · Concurrency and async
- async and await: waiting without blockingC# and .NET · Exceptions, delegates, events and async
- Concurrency 1: threads, mutexes and data racesC++ Programming (modern C++ and DSA) · Exceptions, concurrency and building projects with CMake
- Concurrency interview questions and coding roundsGo Programming (backend and cloud) · Careers and interviews
Questions people ask about this
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
What runs Java bytecode?Related
Answer: The Java Virtual Machine (JVM)
Bytecode runs on the JVM.
What happens to running threads when main returns?Related
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.
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
- This is the last topic of “Multithreading and concurrency”. The module exam comes next.
- Module 9 exam: Multithreading and concurrency · 12 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, JDBC, Maven, Gradle and JUnit. 60 topics left in the course before the final exam.