Java has always been powerful for building large-scale applications, but writing highly concurrent programs was never easy. Creating thousands of threads was expensive, and managing them required complex code. That all changed with Java 21 and the introduction of Virtual Threads — one of the biggest improvements to the Java platform in years.
In this post, we will understand what virtual threads are, how they work internally, how to create them with code examples, and the most important interview questions companies are asking about them in 2026.
Table of Contents
1. What are Virtual Threads in Java?
Virtual threads are lightweight threads managed by the JVM, not by the operating system. They were introduced as part of Project Loom and became a standard (non-preview) feature in Java 21 (JDK 21).
A virtual thread is a thread that is managed entirely by the Java runtime — it does not map 1:1 to an OS thread. The JVM can create millions of virtual threads with very little memory overhead.
Think of it like this: traditional threads (called platform threads) are like renting an entire apartment for every task. Virtual threads are like hot-desking — the same physical desk can be used by many people, one at a time, with zero conflict.
2. Why Were Virtual Threads Introduced?
Before virtual threads, Java developers faced a classic problem:
- Creating too many threads → crashes the server (each OS thread uses ~1MB of stack memory)
- Using thread pools → limits scalability, complex to tune
- Reactive/async code → solves performance but is very hard to read and debug
Project Loom’s Goal
Enable simple blocking-style code to scale like complex async code — without any rewriting.
Virtual threads solve this by making blocking operations (like database calls, API calls, file reads) extremely cheap. When a virtual thread is blocked, the JVM simply unmounts it from the underlying OS thread, freeing that thread to do other work. When the blocking is done, the virtual thread gets remounted and continues.
3. Virtual Thread vs Platform Thread
| Property | Platform Thread | Virtual Thread |
|---|---|---|
| Managed by | Operating System | JVM (Java Runtime) |
| Memory per thread | ~1 MB stack size | A few KB (very small) |
| Max threads you can create | Thousands only | Millions easily |
| Creation cost | Expensive | Very cheap |
| Context switching | OS-level (expensive) | JVM-level (lightweight) |
| Best for | CPU-intensive tasks | I/O-bound tasks |
| Thread pooling needed? | Yes (ExecutorService) | No — create freely |
4. How to Create Virtual Threads in Java?
Java provides multiple ways to create virtual threads. Let’s look at all of them:
Method 1: Using Thread.startVirtualThread()
public class VirtualThreadDemo {
public static void main(String[] args) {
// Simplest way to start a virtual thread
Thread vt = Thread.startVirtualThread(() -> {
System.out.println("Hello from Virtual Thread: "
+ Thread.currentThread());
});
vt.join(); // wait for it to finish
}
}
Method 2: Using Thread.ofVirtual()
public class VirtualThreadDemo2 {
public static void main(String[] args) throws InterruptedException {
// Build a named virtual thread
Thread vt = Thread.ofVirtual()
.name("my-virtual-thread")
.start(() -> {
System.out.println("Running in: "
+ Thread.currentThread().getName());
System.out.println("Is virtual? "
+ Thread.currentThread().isVirtual());
});
vt.join();
}
}
Output
Running in: my-virtual-thread
Is virtual? true
Method 3: Creating Many Virtual Threads (Real-world use)
import java.util.ArrayList;
import java.util.List;
public class ManyVirtualThreads {
public static void main(String[] args) throws InterruptedException {
List<Thread> threads = new ArrayList<>();
// Create 1000 virtual threads — no problem!
for (int i = 0; i < 1000; i++) {
int taskId = i;
Thread vt = Thread.startVirtualThread(() -> {
// Simulating I/O work (e.g. DB call)
System.out.println("Task " + taskId + " running");
});
threads.add(vt);
}
// Wait for all to complete
for (Thread t : threads) {
t.join();
}
System.out.println("All 1000 virtual threads completed!");
}
}
5. Using Virtual Threads with ExecutorService
Java 21 added a new executor that creates a fresh virtual thread for every submitted task:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class VirtualThreadExecutor {
public static void main(String[] args) {
// New in Java 21 — virtual thread per task executor
try (ExecutorService executor =
Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 10; i++) {
int taskId = i;
executor.submit(() -> {
System.out.println("Processing request #" + taskId
+ " on " + Thread.currentThread());
});
}
} // executor auto-closes here
}
}
Note
With newVirtualThreadPerTaskExecutor(), you do NOT need to worry about pool size. Every task automatically gets its own virtual thread.
6. When to Use Virtual Threads?
Virtual threads are perfect when your application spends most of its time waiting — waiting for a database response, an HTTP call, file I/O, etc.
Best Use Cases
Chat applications, notification systems with many connections
Web servers handling thousands of simultaneous HTTP requests
Microservices that make multiple downstream API calls
Applications with heavy database read/write operations
7. When NOT to Use Virtual Threads?
Avoid Virtual Threads When:
Synchronized blocks with pinning — When a virtual thread enters a synchronized block and blocks, it gets “pinned” to the carrier thread, reducing scalability.
CPU-intensive tasks — If your code does heavy number crunching (sorting, encryption, image processing), virtual threads won’t help. The bottleneck is the CPU, not waiting.
ThreadLocal abuse — ThreadLocal variables that store large objects can cause memory issues since millions of virtual threads may be created.
8. Java Virtual Threads — Interview Questions & Answers
These are the most asked virtual thread interview questions in Java interviews in 2026:
Q1. What are virtual threads in Java and which version introduced them?
Virtual threads are lightweight threads managed entirely by the JVM (not the OS). They were introduced as a preview feature in Java 19, and became a stable, production-ready feature in Java 21 as part of Project Loom. Unlike platform threads, virtual threads have very low memory overhead and can be created in millions without crashing the JVM.
Q2. What is the difference between a virtual thread and a platform thread?
A platform thread maps 1:1 to an OS thread and uses ~1MB of stack memory. You can realistically create only thousands of them. A virtual thread is managed by the JVM, uses just a few KB, and you can create millions. Virtual threads are best for I/O-bound tasks; platform threads are still preferred for CPU-intensive work.
Q3. What is Project Loom in Java?
Project Loom is a JVM project that aims to add lightweight concurrency primitives to Java. Its main deliverable is virtual threads. The goal of Project Loom is to let developers write simple, readable, sequential blocking code that scales just as well as complex async/reactive code — without any code rewrites.
Q4. What is a carrier thread in virtual threads?
A carrier thread is the actual OS (platform) thread that executes a virtual thread at any given moment. The JVM maintains a small pool of carrier threads (usually equal to the number of CPU cores). Virtual threads are mounted onto a carrier thread to execute, and unmounted when they block. This is what makes virtual threads so scalable — many virtual threads share a small number of carrier threads
Q5. What is thread pinning in virtual threads?
Thread pinning happens when a virtual thread cannot be unmounted from its carrier thread during a blocking operation. This occurs when the virtual thread is inside a synchronized block or is calling a native method. Pinned threads hold a carrier thread, which reduces scalability. To avoid this, Java recommends using ReentrantLock instead of synchronized blocks in highly concurrent code.
Q6. Can virtual threads replace thread pools completely?
For I/O-bound workloads, yes — you no longer need to carefully size a thread pool. You can simply create a new virtual thread for every request. However, for CPU-bound workloads, thread pools with platform threads are still the right choice. Virtual threads do not improve performance for tasks that are actively computing — only for tasks that spend time waiting.
Q7. How do you check if a thread is a virtual thread in Java?
You can use the isVirtual() method available on the Thread class since Java 21:
Thread.currentThread().isVirtual(); // returns true if virtual
9. Conclusion
Java Virtual Threads are one of the most impactful additions to Java in the last decade. They solve the age-old scalability problem in a beautifully simple way — no reactive frameworks, no callback hell, no complex async code. Just simple, readable, blocking code that magically scales to handle millions of concurrent operations.
If you are preparing for Java interviews in 2026, understanding virtual threads deeply — including Project Loom, carrier threads, thread pinning, and the difference from platform threads — will put you well ahead of most candidates.
Key Takeaways
Avoid them for CPU-intensive tasks and be careful with synchronized blocks (pinning)
Virtual threads are lightweight, JVM-managed threads introduced in Java 21
They are part of Project Loom and are ideal for I/O-bound work
You can create millions of them with minimal overhead
Use Thread.startVirtualThread() or Thread.ofVirtual() to create them