Introduction
If Java 8 gave us Streams and CompletableFuture, then Java 21 gives us something even more fundamental:
A completely new way of thinking about concurrency.
For over twenty years, Java developers learned the same rules:
- Threads are expensive.
- Never create one thread per request.
- Always use thread pools.
- Reuse threads.
- Asynchronous programming scales better.
- Reactive programming solves thread scalability.
These rules shaped nearly every enterprise Java application.
Every Spring Boot application.
Every banking platform.
Every payment gateway.
Every insurance system.
Every trading platform.
But what if those assumptions were no longer true?
That is exactly what Project Loom set out to challenge.
Virtual Threads allow developers to write simple, familiar blocking code while achieving levels of scalability that previously required asynchronous or reactive programming.
This is not simply another API.
It is a fundamental change to the Java runtime.
Learning Objectives
By the end of this article you will understand:
- Why platform threads are expensive.
- Why thread pools exist.
- What Virtual Threads are.
- How Virtual Threads work internally.
- Carrier Threads.
- Thread Parking.
- Thread Pinning.
- Spring Boot integration.
- Banking microservices architecture.
- Database considerations.
- Performance expectations.
- Migration strategy.
- Best practices.
A Typical Banking Architecture Today
Imagine an online banking platform.
Customer
│
▼
API Gateway
│
┌───────────────┼────────────────┐
▼ ▼ ▼
Account Service Payment Service Customer Service
│ │ │
├───────────────┼────────────────┤
▼ ▼ ▼
Oracle DB Kafka Redis Cache
Every REST request creates work.
Each request executes on a Java thread.
The Traditional Request Model
Suppose 1,000 customers send requests simultaneously.
Traditional Spring Boot:
1000 Requests
↓
200 Tomcat Threads
↓
Thread Pool
↓
Waiting Queue
Only 200 requests execute.
The remaining requests wait.
Why?
Because operating system threads are expensive.
Each Platform Thread requires:
- Native memory
- Stack memory
- Scheduler management
- Context switching
Creating 100,000 platform threads would consume enormous resources.
What Happens During a Database Call?
Consider this code.
Customer customer = repository.findById(id);
Looks simple.
Internally:
Java Thread
↓
JDBC
↓
Network
↓
Oracle
↓
Wait...
↓
Response
Notice something important.
During the database call:
The thread is doing absolutely nothing.
It simply waits.
Waiting Is the Biggest Problem
Suppose:
Database latency:
40 milliseconds
CPU work:
2 milliseconds
The thread spends:
2 ms Working
40 ms Waiting
Almost all its lifetime is spent waiting.
Why Thread Pools Were Invented
Since threads are expensive:
Developers created pools.
200 Threads
↓
Reuse
↓
Next Request
↓
Reuse
↓
Next Request
Instead of constantly creating threads.
This worked well.
But it also imposed limits.
The Async Revolution
To overcome thread limitations we introduced:
- Future
- CompletableFuture
- Reactive Streams
- Project Reactor
- WebFlux
Instead of waiting:
Thread
↓
Call Database
↓
Wait
we wrote asynchronous code.
Thread
↓
Start Work
↓
Return
↓
Resume Later
More scalable.
But significantly more complex.
Project Loom Asked One Question
Instead of making code asynchronous…
Why not make threads cheap?
Platform Thread vs Virtual Thread
Traditional Java:
Java Thread
↓
Operating System Thread
One-to-one relationship.
Virtual Thread:
100,000 Virtual Threads
↓
10 Carrier Threads
↓
Operating System
This is the key innovation.
What Is a Carrier Thread?
Virtual Threads do not run directly on the operating system.
Instead:
Virtual Thread
↓
Carrier Thread
↓
Operating System Thread
Carrier threads execute Virtual Threads.
When one Virtual Thread blocks…
another Virtual Thread can immediately use the same carrier.
Parking Instead of Blocking
Traditional thread:
Database Call
↓
OS Thread Waits
Virtual Thread:
Database Call
↓
Virtual Thread Parks
↓
Carrier Thread Released
↓
Runs Another Virtual Thread
This dramatically improves CPU utilization.
Example
Traditional:
ExecutorService executor =
Executors.newFixedThreadPool(200);
Virtual Threads:
ExecutorService executor =
Executors.newVirtualThreadPerTaskExecutor();
That’s all.
The programming model remains familiar.
Spring Boot Example
Controller:
@GetMapping("/accounts/{id}")
public Account getAccount(Long id){
return accountService.findById(id);
}
No callback.
No Reactor.
No CompletableFuture.
No reactive chains.
Blocking code.
Yet highly scalable.
Banking Example
Customer transfers money.
Transfer Request
↓
Payment Service
↓
Fraud Service
↓
Customer Service
↓
Oracle
↓
Kafka
↓
Response
Each downstream call waits.
With Platform Threads:
Every wait consumes a thread.
With Virtual Threads:
Waiting threads park.
Carrier threads continue processing other requests.
Does This Replace CompletableFuture?
No.
CompletableFuture solves:
- Parallel execution
- Asynchronous composition
- Multiple concurrent tasks
Virtual Threads solve:
- Thread scalability
The two technologies complement each other.
Does This Replace Reactive Programming?
Not completely.
Reactive programming still provides advantages such as:
- Backpressure
- Stream processing
- Reactive pipelines
However…
Many applications adopted reactive programming primarily to avoid blocking platform threads.
Virtual Threads reduce that motivation considerably.
For many CRUD-style microservices, a simple blocking programming model with Virtual Threads is easier to understand and maintain.
What About JDBC?
This surprises many developers.
Blocking JDBC code works well with Virtual Threads.
Why?
Because while the Virtual Thread waits for the database:
Virtual Thread
↓
Parked
↓
Carrier Thread Free
The carrier thread is available for other work.
But Connection Pools Still Matter
Virtual Threads are inexpensive.
Database connections are not.
Example:
100,000 Virtual Threads
↓
50 Database Connections
Only fifty SQL statements can execute simultaneously.
Virtual Threads do not eliminate database connection limits.
Tomcat + Virtual Threads
Spring Boot 3.2 introduced support for Virtual Threads.
Tomcat can dispatch incoming requests to Virtual Threads instead of traditional platform threads.
Architecture:
HTTP Request
↓
Tomcat
↓
Virtual Thread
↓
Business Logic
↓
JDBC
↓
Oracle
No application code changes are required in many cases.
Performance Expectations
Virtual Threads improve:
- Thread scalability
- Memory consumption
- Context switching
- Simplicity
They do not make:
- SQL queries faster.
- Networks faster.
- Remote services faster.
They make better use of waiting time.
Thread Pinning
One concept every enterprise developer should know is Thread Pinning.
Certain operations can prevent a Virtual Thread from releasing its carrier thread.
Examples include:
- Long-running synchronized blocks
- Some native calls
- Operations that cannot safely park
Pinned threads reduce the scalability benefits of Virtual Threads.
When adopting Loom, review long synchronized sections and blocking native integrations.
Should Existing Applications Migrate?
For most Spring Boot applications:
Yes—but gradually.
Start with:
- New microservices.
- Internal APIs.
- Read-heavy services.
Benchmark before migrating high-throughput production systems.
Banking Architecture with Virtual Threads
API Gateway
│
▼
Virtual Thread
│
┌──────────────────┼─────────────────┐
▼ ▼ ▼
Customer Service Payment Service Fraud Service
│ │ │
└──────────────────┼─────────────────┘
▼
Oracle / Kafka
Instead of hundreds of platform threads, the application can efficiently manage tens or hundreds of thousands of lightweight Virtual Threads.
Common Misconceptions
“Virtual Threads make my code faster.”
Not necessarily.
They improve scalability, not CPU speed.
“I no longer need connection pools.”
Incorrect.
Connections remain expensive resources.
“Reactive Programming is obsolete.”
No.
Reactive programming continues to excel for streaming, event-driven systems, and scenarios requiring backpressure.
Virtual Threads simply make synchronous programming viable at much higher levels of concurrency.
“I can create unlimited Virtual Threads.”
They are lightweight—but not free.
Good architecture and capacity planning still matter.
Best Practices
✔ Use Virtual Threads for request-per-thread server applications.
✔ Continue using JDBC where appropriate.
✔ Keep connection pools properly sized.
✔ Avoid unnecessary long synchronized blocks.
✔ Benchmark before migrating critical systems.
✔ Prefer clarity over premature optimization.
Interview Questions
Why were Virtual Threads introduced?
To dramatically reduce the cost of concurrency while preserving Java’s familiar thread-per-request programming model.
What is the difference between a Platform Thread and a Virtual Thread?
Platform Threads map one-to-one with operating system threads.
Virtual Threads are lightweight JVM-managed threads multiplexed onto a smaller number of carrier threads.
Do Virtual Threads replace CompletableFuture?
No.
CompletableFuture is designed for asynchronous composition and parallel task orchestration, while Virtual Threads address the scalability of blocking operations.
Can Virtual Threads eliminate the need for Reactive Programming?
Not entirely.
They simplify many synchronous microservices, but reactive programming remains valuable for streaming, backpressure, and event-driven architectures.
What is Thread Pinning?
Thread Pinning occurs when a Virtual Thread cannot release its carrier thread during a blocking operation, reducing scalability.
Should every Spring Boot microservice migrate immediately?
No.
Evaluate each application, benchmark realistic workloads, and adopt Virtual Threads where they provide measurable benefits.
Summary
Virtual Threads represent one of the most significant changes in Java’s history. They challenge long-standing assumptions about thread management by making thread-per-request architectures practical again at massive scale.
For enterprise Spring Boot applications, especially in banking and financial services, Virtual Threads simplify concurrency, improve scalability, and reduce the need for complex asynchronous code. They do not replace good architecture, efficient databases, or proper resource management, but they fundamentally change how Java developers can think about handling concurrent workloads.
Project Loom is more than a new API—it is a new concurrency model that will shape the future of enterprise Java development for years to come.
Coming Up Next
Part 42 – Modern Java Migration Guide – Upgrading Spring Boot Microservices from Java 8/11/17 to Java 21
We’ll bring together everything you’ve learned throughout this series and explore:
- Migration strategies
- Compatibility considerations
- Framework upgrades
- Performance tuning
- Spring Boot 3.x recommendations
- Jakarta EE migration
- Production rollout patterns
- Banking migration checklist
- Common pitfalls
- Enterprise best practices
This article will serve as the practical bridge between learning modern Java features and successfully adopting them in real-world enterprise microservices.