Part 37: Java 21 – Virtual Threads – Revolutionizing Spring Boot Microservices and Enterprise Banking Applications

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.

Leave a Reply

Your email address will not be published. Required fields are marked *