
Till recently whenever we used to start a java thread , it will internally request operating system to start the OS thread . From that point of time OS is responsible to manage this thread just like any other OS thread. So we can easily say one Java thread has one to one mapping to OS thread. We call these Java threads as Platform threads are heavy in nature and also tied with the stack space in the JVM per thread. This makes platform threads resource intensive and does not scale up. Also can load the system when you have extreme concurrency and blocking call requirements.
Virtual threads is the answer, virtual threads are light weight. Virtual thread is mounted on the platform thread, now the platform thread is called as carrier thread. Typically JVM will create the platform threads not more than your CPU cores. When virtual thread is finished its execution it is unmounted by JVM so that platform thread is available for other virtual threads, at this point of time a finished Virtual thread is available for GC.
JVM can mount/unmount virtual threads from the platform threads for better utilization of resources and fair share to all virtual threads. During mounting and unmounting process the instruction pointer and stack space is copied in between the Heap and Platform threads stack space. As a developer you have no control on carrier threads and scheduling of virtual threads on them.
From a system designer’s lens, Virtual Threads are not just about developer convenience; they directly impact scalability, cost, and architecture choices. Due to massive concurrency, simplified code, better Resource Utilization and system cost optimization
Performance Benchmarks
In few of my internal tests and from community :
- Startup cost: Creating 1 million Virtual Threads takes less than a second.
- Memory footprint: A million Virtual Threads may consume less than 2 GB of RAM.
- Throughput: Comparable or better than async/reactive frameworks, but with simpler code. Thanks to seamless integration with Completable Future in Java.
- Latency: Lower tail latency due to reduced contention on OS threads.
- Throughput Benefits : If virtual threads are used only for CPU operations there is no significant benefit which can stand apart from traditional threading model. If your virtual thread is performing blocking operations then we get extremely good results and real performance benefits , as virtual threads avoid OS level context switches during thread scheduling with significant lesser memory footprints. All this smart work is done internally by JVM and limiting creation of platform threads to not more than cpu cores.
- Without careful system-level design, Virtual Threads can overwhelm external systems (like databases).
| Aspect | Platform Threads | Virtual Threads |
|---|---|---|
| Backed By | OS threads (1:1 mapping) | JVM scheduler (M:N mapping) |
| Memory Usage | ~1 MB stack per thread | ~KBs per thread |
| Creation Cost | High | Very low |
| Blocking I/O | Blocks OS thread | Yields carrier thread |
| Scalability | Thousands | Millions |
| Debugging | Complex with async/reactive | Natural, like normal threads |
Migration Strategy for Enterprises
For large systems, adopting Virtual Threads should be strategic:
- Identify Pain Points
- Look for async/reactive code that became too complex.
- Find bottlenecks in blocking I/O layers.
- Pilot Migration
- Replace async code in a microservice with Virtual Threads.
- Measure performance, simplicity, and maintainability gains.
- Progressive Adoption
- Expand to more services gradually.
- Train developers on pitfalls (e.g.,
ThreadLocal, library compatibility).
- Monitoring & Observability Upgrade
- Enhance observability stack to handle millions of threads.
- Focus on aggregated metrics instead of per-thread metrics.
- Production Rollout
- Once confident, standardize Virtual Threads across I/O-bound workloads.
System Design Use Cases
Let’s examine where Virtual Threads can shine in real-world architectures:
1. Web Servers / APIs
- Traditional servlet containers (e.g., Tomcat, Jetty) used complex async APIs for scalability.
- With Virtual Threads, a thread-per-request model is viable again.
- Simple code + massive scalability.
2. Database Access
- JDBC is inherently blocking.
- With Virtual Threads, each DB call can run in its own lightweight thread without wasting OS resources.
- Simplifies data-access layers.
3. Microservices
- In microservices, services often wait on network I/O.
- Virtual Threads allow thousands of simultaneous API calls without reactive frameworks.
4. Messaging Systems
- Kafka, RabbitMQ, or JMS consumers can each run in their own Virtual Thread.
- Easier concurrency handling for message processing.
5. Simulation & Event Systems
Large-scale simulations requiring millions of concurrent entities can now be modeled as Virtual Threads.
Conclusion
Virtual Threads in Java 21 mark a paradigm shift in how we design and scale systems. They combine the simplicity of synchronous code with the scalability of asynchronous models.
As a System Designer, I see Virtual Threads not as a replacement for all concurrency tools, but as a powerful new default for I/O-bound workloads. They simplify system design, improve scalability, and reduce operational costs — all while making developers’ lives easier.
If threads were once considered a scarce, heavyweight resource, Virtual Threads make them as abundant as objects. This opens the door to rethinking architectures that were previously constrained by thread limitations.
References
Oracle Docs – Virtual Threads (Java SE 21)
Official documentation on virtual threads in Java 21 (JEP 444) — definitions, usage, scheduling, debugging.
https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html
Baeldung – Working with Virtual Threads in Spring
Tutorial showing how to enable and use virtual threads within Spring Boot / Spring framework context.
https://www.baeldung.com/spring-6-virtual-threads