For a long time, I was confident that I understood how concurrency worked in Java.
Create a thread.
Start it.
Join it.
After that, a thread pool handled the growing workload.
Simple… right?
Then I started reading about Virtual Threads in Java (Project Loom), and suddenly I realized — we’ve been living with limitations we just accepted as “normal”.
This post is my attempt to explain:
- What Platform Threads are
- What problems virtual threads solve
- How they differ
- And when it makes sense to use one over the other
not like documentation, but like how I actually understood it.
1. Platform Threads
In Java, the classic thread model is called a Platform Thread. These are traditional threads backed directly by the Operating System.
Every Java developer starts with this:
Thread thread = new Thread(() -> {
doWork();
});
thread.start(); // Starts a new platform thread
It feels powerful at first. You’re doing real parallel work. Actual multitasking.
But then the cracks appear.
- Creating too many threads? ❌ App slows down.
- Thousands of requests? ❌ Thread pool exhausted.
- Blocking I/O? ❌ Threads just sit there doing nothing.
Why...?
Because platform threads are directly mapped to OS threads.
That means:
- One Java thread = one OS thread.
- OS threads are expensive.
- Each thread has a big stack and scheduling cost.
So instead of writing simple code, we started doing tricks:
- Fixed-size thread pools.
- Async APIs.
- Reactive programming.
- Complicated execution models.
Not because we wanted to — but because threads didn’t scale.
The Question That Couldn’t Be Ignored
If a thread is waiting for I/O, why is it still blocking an OS thread?
During I/O operations, a thread:
- isn’t doing any computation.
- isn’t using the CPU.
- is simply waiting for an external operation to complete.
So why is it treated like a scarce system resource?
If the thread isn’t actively running, why should it continue holding an OS thread?
That question is exactly where Virtual Threads come in.
2. Virtual Threads — Lightweight & Scalable
Virtual threads are super-light, JVM-managed threads that let us handle millions of concurrent tasks without worrying about OS thread overhead.
Creating one is straightforward:
Thread thread = Thread.startVirtualThread(() -> {
doWork();
});
That’s it.
- Same programming model.
- Same blocking style.
- Totally different execution story.
Virtual threads are:
- Created by the JVM
- Not permanently attached to OS threads
- Extremely cheap compared to platform threads
And that changes everything.
What Actually Happens?
Virtual threads run on top of platform threads, called carrier threads.
When a virtual thread:
- is running -> it’s mounted on a carrier thread.
- hits a blocking operation -> it gets parked.
- is unblocked -> it resumes on any available carrier thread.
So instead of blocking an OS thread while waiting, the JVM simply moves on.
In simple terms:
- Platform threads block the system.
- Virtual threads block only themselves.
This solves the core scalability problem of platform threads.
- Blocking I/O no longer wastes OS threads.
- Thread pools stop being a hard limit.
- Simple blocking code can scale to massive concurrency.
Virtual threads don’t remove blocking — they remove the cost of blocking.
That’s the breakthrough.
3. How They Differ?
This difference is easier to see in the diagram below - notice what happens during blocking.
4. When it makes sense to use one over the other?
Virtual threads are a great default for most modern applications — especially when the workload is I/O-heavy.
They make sense when:
- Handling many concurrent requests
- Waiting on databases, APIs, or file systems
- Building web servers, APIs, or microservices
- Wanting simple, blocking code that still scales
Platform threads still matter, though.
They make sense when:
- The work is CPU-intensive
- Threads need tight interaction with the OS
- Thread pinning or native calls are involved
In practice, it comes down to this:
- Use virtual threads for waiting.
- Use platform threads for computing.
That rule alone covers most real-world decisions.
Final take
Virtual threads didn’t make Java faster.
They just stopped punishing us for blocking.
We can write simple code.
We can wait on I/O.
And the JVM handles the mess.
No thread pool anxiety.
No async gymnastics.
No PhD in callbacks.
Same Java.
Same threads (mostly).
Just way fewer headaches.
That’s it.
If this helped you even a little, hit ❤️, drop a comment, or share your thoughts below.

Top comments (0)