Backend Development 8 min read

Introducing Java 21 Virtual Threads: Basics, Spring Boot Integration, and Performance Comparison

Java 21's virtual threads provide lightweight, high‑concurrency execution managed by the JVM, and this article explains their fundamentals, demonstrates basic creation, shows how to enable them in Spring Boot, and presents performance benchmarks comparing virtual threads to traditional threads.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Introducing Java 21 Virtual Threads: Basics, Spring Boot Integration, and Performance Comparison

Virtual threads, introduced in Java 21, are a new feature that simplifies concurrent programming by being lightweight, highly concurrent, and automatically managed by the JVM.

Lightweight: Virtual threads are managed by the JVM rather than the operating system, resulting in far lower memory usage and creation cost, allowing creation of hundreds of thousands of threads.

High concurrency: They handle high‑concurrency scenarios, especially I/O‑intensive applications, making them ideal for responsive, high‑throughput services.

Automatic management: No manual thread‑pool handling is required; the JVM automatically schedules virtual threads based on load, simplifying concurrency code.

Basic usage of virtual threads

Creating a virtual thread is straightforward; you can start it similarly to a traditional thread, but its creation and start are much lighter:

Thread virtualThread = Thread.ofVirtual().start(() -> {
    System.out.println("Virtual thread is running");
});
System.out.println("Main thread is running");

Virtual thread delayed start:

Thread virtualThread = Thread.ofVirtual()
    .name("VirtualThread")
    .unstarted(() -> System.out.println("Virtual thread running"));
virtualThread.start();
virtualThread.join(); // wait for virtual thread to finish

Using virtual threads in Spring Boot

To use virtual threads in a Spring Boot project you need a few simple configurations.

1. Enable preview mode in pom.xml :

org.apache.maven.plugins
maven-compiler-plugin
21
21
--enable-preview

2. Enable performance metrics in application.properties :

management.endpoints.web.exposure.include=health,info,metrics

3. Configure Tomcat to use a virtual‑thread executor:

@Bean
public TomcatProtocolHandlerCustomizer
protocolHandlerVirtualThreadExecutorCustomizer() {
    return protocolHandler -> protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
}

Experiment: Traditional threads vs virtual threads

1. Create 100,000 threads and execute

Traditional threads:

for (int i = 0; i < 100_000; i++) {
    Thread thread = new Thread(() -> System.out.println(i));
    thread.start();
    thread.join();
}
Execution time ≈ 18.6 seconds.

Virtual threads:

for (int i = 0; i < 100_000; i++) {
    Thread thread = Thread.ofVirtual().unstarted(() -> System.out.println(i));
    thread.start();
    thread.join();
}
Execution time ≈ 3.7 seconds, about 500% faster.

2. HTTP request performance comparison

In high‑concurrency scenarios virtual threads show clear advantages. The test sent 1,600 HTTP requests with 400 concurrent workers.

Configuration of HTTP thread executor:

@Bean
public TomcatProtocolHandlerCustomizer
protocolHandlerVirtualThreadExecutorCustomizer() {
    return protocolHandler -> protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
}

Results:

Traditional threads: total time 9.659 s, 165.65 requests/s

Virtual threads: total time 7.912 s, 202.22 requests/s

Virtual threads significantly increase throughput and reduce response time.

Other Java performance techniques

Besides virtual threads, several other techniques can improve Java performance, especially for Spring Boot high‑concurrency workloads:

Parallel streams: use parallelStream() for CPU‑bound tasks. List numbers = Arrays.asList(1,2,3,4,5); numbers.parallelStream().forEach(n -> System.out.println(n * 2));

Asynchronous programming with CompletableFuture: suitable for I/O‑bound tasks. CompletableFuture future = CompletableFuture.runAsync(() -> { // async task System.out.println("Async task completed"); }); future.join(); // wait for completion

Database query optimization: reduce query count and use caches such as Redis.

Memory management: employ object pools (e.g., Apache Commons Pool) to reuse resources.

Summary

Virtual threads are a breakthrough for Java concurrency, simplifying thread management and boosting performance in high‑concurrency scenarios.

They allow creation of hundreds of thousands of threads without degrading application performance.

Enabling virtual threads in Spring Boot requires only a few configuration steps and yields noticeable speed gains.

Additional optimizations like parallel streams, CompletableFuture, query caching, and object pooling further enhance Java application performance.

By applying these techniques, Spring Boot applications can achieve stronger performance and lower latency under heavy load.

JavaJVMperformanceconcurrencySpring BootVirtual Threads
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.