Traditional Threads vs. Virtual Threads: A Performance Benchmark on Spring Boot 4
Bottom line: For high-concurrency Spring Boot apps on modern Java, virtual threads usually deliver higher throughput, lower latency, and better resource efficiency than traditional threads—provided you enable them correctly and benchmark against your real workload.
Java’s concurrency story changed with Project Loom and virtual threads, and Spring Boot 4 (evolving from the Spring Boot 3.2+ line on Java 21+) is designed to take advantage of this new model. This post breaks down how traditional threads and virtual threads differ, how they behave under load in a Spring Boot service, and what to look for when running your own performance benchmarks.
What Are Traditional Threads in Java?
Traditional Java threads map one-to-one to operating system threads and are scheduled directly by the OS. Each platform thread consumes a relatively large stack and incurs non-trivial context-switching overhead, which limits how many concurrent requests your Spring Boot service can handle efficiently.
In a typical Spring Boot app using the Servlet stack (Tomcat, Jetty, or Undertow), each incoming HTTP request is processed by a worker from a fixed-size thread pool. Under high concurrency (for example, thousands of simultaneous requests), that pool can saturate, leading to increased queueing, higher tail latency, and rejected tasks.
What Are Virtual Threads (Project Loom)?
Virtual threads are lightweight threads introduced as a standard feature in Java 21 as part of Project Loom. They are scheduled by the JVM and multiplexed over a much smaller number of carrier (OS) threads, allowing your application to create hundreds of thousands of concurrent threads without overwhelming the operating system.
When a virtual thread performs blocking I/O, it parks and detaches from its carrier thread so the OS thread can do other work, which greatly improves scalability for I/O-bound workloads such as HTTP calls and database queries. Benchmarks and research show that, in these scenarios, virtual threads often deliver higher throughput and lower latency compared to traditional thread pools.
How Does Spring Boot Use Virtual Threads?
Recent Spring Boot versions (3.2+ and their Spring Boot 4 successors) support virtual threads as a first-class execution model for handling requests and background tasks. Instead of relying solely on a limited pool of platform threads, Spring can be configured to run each request on its own virtual thread, effectively enabling “one request = one virtual thread.”
Under the hood, Spring integrates virtual threads into its task executors and servlet container configuration, while letting you keep the familiar imperative programming model of Spring MVC. That means you can write synchronous code that looks blocking, but scales similarly to highly optimized reactive stacks for many I/O-heavy applications.
Performance Benchmark Setup on Spring Boot + JDK 21
To compare traditional threads vs. virtual threads in a realistic way, you need a repeatable benchmark setup. A typical environment includes:
-
Java 21 with virtual threads enabled in your runtime configuration.
-
A Spring Boot HTTP API that performs blocking I/O, such as JDBC queries or calls to external REST services.
-
A load-testing tool (for example, JMeter, Gatling, or k6) generating thousands of concurrent requests.
You then run the same application in two modes—one using platform-thread pools and one using virtual threads—and collect metrics such as requests per second, p95/p99 latency, memory usage, and CPU utilization under identical load.
Benchmark Results: Throughput, Latency, and Memory
Public benchmarks and academic analyses reveal consistent patterns when comparing the two threading models.
-
Throughput: Virtual-thread-based servers often handle significantly more requests per second, especially for I/O-bound workloads, sometimes achieving 1.5x–2x throughput compared to traditional thread pools.
-
Latency: High-percentile latencies (p95, p99) are typically lower with virtual threads because fewer requests are stuck waiting in queues while thread pools are exhausted.
-
Memory footprint: Virtual threads use far less memory per thread than platform threads, which helps keep total memory usage under control at very high concurrency levels.
Some Spring and Netty benchmarks even show virtual-thread-based setups performing competitively with or better than reactive programming models for many real-world APIs. However, if you spawn huge numbers of virtual threads without any backpressure and with a small heap, you can still hit memory limits or garbage-collection pressure, so careful tuning remains important.
Example: Configuring Virtual Threads in Spring Boot
While the exact configuration can vary by version, the general pattern in a Spring Boot 4-style application on Java 21 is to enable virtual threads in your thread executor.
Conceptually, you will:
-
Enable virtual-thread support via Spring properties or configuration (for example, a setting that tells Spring to use a virtual-thread-based executor).
-
Customize your
TaskExecutororAsyncTaskExecutorbean to create a new virtual thread per task, so each request runs in its own lightweight thread.
With this setup, your controllers can stay imperative and blocking, while the underlying runtime uses virtual threads for scalable concurrency. You can then run your benchmarks by toggling between the virtual-thread executor and a traditional thread-pool executor to see the difference in your own metrics.
When to Choose Virtual Threads vs. Traditional Threads
Virtual threads are a powerful tool, but they are not a universal solution to every performance problem.
Virtual threads are usually the better choice when:
-
Your Spring Boot APIs are primarily I/O-bound and must support large numbers of concurrent users.
-
You want to keep simple, synchronous Spring MVC code instead of adopting a full reactive programming model.
-
Your production environment already runs on Java 21 or later and uses Spring Boot 3.2+ / 4-era baselines.
Traditional threads may still make sense when:
-
Your workload is CPU-bound and the bottleneck is raw processing time rather than blocking I/O.
-
You depend on libraries that are not yet virtual-thread-friendly or rely on thread-local behavior that assumes a small fixed thread pool.
-
You cannot yet upgrade your stack to Java 21 in production.
FAQ: Virtual Threads, Loom, and Spring Boot 4
Q1. Are virtual threads always faster than traditional threads?
No; they provide the biggest gains for I/O-bound, high-concurrency workloads, but CPU-bound tasks may see limited improvement compared to well-tuned platform-thread pools.
Q2. Do virtual threads replace reactive frameworks like WebFlux?
Virtual threads remove many of the original reasons to adopt reactive-only programming, but reactive stacks still offer advantages for streaming, complex backpressure, and some specialized use cases.
Q3. What Java version is required for virtual threads in Spring Boot?
Virtual threads are a standard feature in Java 21, and modern Spring Boot lines are optimized to use them, so Java 21 or later is strongly recommended.
Q4. How can I verify that my Spring Boot app is actually using virtual threads?
You can inspect thread names, use profiling tools, or enable detailed logging around your executors and servlet container to confirm that request-handling threads are virtual.
Q5. Can virtual threads cause memory or stability issues?
They can if your application spawns massive numbers of virtual threads with no limits and runs with an undersized heap; you still need sensible backpressure, resource limits, and monitoring.
───
📥 Get Your Free Java Interview Question Guide
Don't just read—learn! Get 10 essential Java interview questions with complete solutions delivered to your inbox. Perfect for interview prep or skill refreshing.
Enter your email below:
<form action="https://formspree.io/f/java-interview-guide" method="POST" style="display:flex; gap:8px; margin:12px 0; flex-wrap:wrap;">
<input type="email" name="email" placeholder="your.email@example.com" required style="flex:1; min-width:200px; padding:10px 12px; border:1px solid #ddd; border-radius:4px; font-size:14px;">
<button type="submit" style="padding:10px 20px; background:#FF6B35; color:white; border:none; border-radius:4px; cursor:pointer; font-weight:600; font-size:14px;">Get Free PDF</button>
</form>
<p style="font-size:12px; color:#666; margin-top:8px;">We'll send you the PDF plus Java tips. Unsubscribe anytime.</p>