What Are Virtual Threads
Lightweight threads in Java 21.
What Are Virtual Threads is a free Java Academy lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Java Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Welcome to Virtual Threads
Virtual threads arrived as a final feature in Java 21 (JEP 444). They are lightweight threads managed by the JVM rather than the operating system.
The goal is simple: let you write straightforward thread-per-request code while still scaling to millions of concurrent tasks.
The Old Problem
A traditional platform thread maps one-to-one to an OS thread. Each one consumes around a megabyte of stack and is expensive to create.
That means you can only run a few thousand at once. Blocking I/O ties up an entire OS thread while it waits.
How Virtual Threads Differ
A virtual thread does not own an OS thread. Many virtual threads are multiplexed onto a small pool of carrier platform threads.
- Cheap to create (thousands per millisecond)
- Tiny memory footprint
- Blocking simply parks the virtual thread, freeing the carrier
Mounting and Unmounting
When a virtual thread runs, it is mounted onto a carrier thread. When it hits a blocking operation, it unmounts, releasing the carrier so other work can proceed.
This park-and-resume happens automatically. Your code reads like plain blocking code.
A First Virtual Thread
Here is the simplest possible example. Thread.ofVirtual().start(...) starts a virtual thread, and join() waits for it.
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread vt = Thread.ofVirtual().start(() ->
System.out.println("Hello from a virtual thread!"));
vt.join();
System.out.println("Main is done.");
}
}Confirming It Is Virtual
Every Thread exposes isVirtual(). This lets you verify what kind of thread your code is running on.
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread vt = Thread.ofVirtual().start(() -> {
System.out.println("isVirtual: " + Thread.currentThread().isVirtual());
});
vt.join();
}
}Scaling to Many Tasks
Because virtual threads are cheap, launching tens of thousands is perfectly normal. Here we spin up 10,000 of them.
import java.util.concurrent.atomic.AtomicInteger;
public class Main {
public static void main(String[] args) throws InterruptedException {
AtomicInteger counter = new AtomicInteger();
Thread[] threads = new Thread[10_000];
for (int i = 0; i < threads.length; i++) {
threads[i] = Thread.ofVirtual().start(counter::incrementAndGet);
}
for (Thread t : threads) t.join();
System.out.println("Completed: " + counter.get());
}
}Naming Virtual Threads
You can name a virtual thread for easier debugging using the builder before starting it.
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread vt = Thread.ofVirtual()
.name("worker-1")
.start(() ->
System.out.println("Running on: " + Thread.currentThread().getName()));
vt.join();
}
}Same API, New Engine
Virtual threads implement the exact same Thread API you already know. You still use start, join, sleep, and interrupt.
This means existing libraries built on Thread and Runnable work without changes.
When To Use Them
Virtual threads shine for I/O-bound workloads: web servers, database calls, remote API requests.
They are not faster for CPU-bound number crunching. There you are still limited by your core count.
Not a Pool
A key mindset shift: you do not pool virtual threads. Create a new one per task and let it die when the task finishes.
Pooling exists to reuse expensive resources. Virtual threads are cheap, so pooling them is an anti-pattern.
Quick Check
Test your understanding of virtual threads.
Recap
You learned what virtual threads are and why they matter:
- Lightweight, JVM-managed, finalized in Java 21
- Multiplexed onto carrier threads via mount/unmount
- Ideal for high-concurrency I/O-bound work
- Same
ThreadAPI, but created per-task rather than pooled
Next you will learn the different ways to create them.
Frequently asked questions
Is the “What Are Virtual Threads” lesson free?
Yes — the full text of “What Are Virtual Threads” is free to read here on the web, and the Java Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Java Academy course, upgrade to CoddyKit PRO.
What will I learn in “What Are Virtual Threads”?
Lightweight threads in Java 21. You practise Java Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Java Academy?
No prior experience is required. Java Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “What Are Virtual Threads” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Java Academy lesson?
Yes. Every Java Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- What Are Virtual Threads
- Creating Virtual Threads
- Platform vs Virtual Threads
- Pitfalls: Pinning and ThreadLocals