September 3, 2026

Spring Boot 3 + Java 21: Virtual Threads and REST API Tutorial (2026)

Learn how to use Java 21 virtual threads with Spring Boot 3 to build REST APIs that handle thousands of concurrent I/O-bound requests without thread-pool exhaustion.

Updated July 7, 2026: Covers Spring Boot 3.3, Java 21 LTS, virtual thread configuration, a sample REST API, and load-testing tips. Part of the Spring Boot 2026 tutorial series.

TL;DR

  • Java 21 virtual threads provide lightweight concurrency for handling large numbers of I/O-bound requests.

  • Spring Boot 3.2+ can enable virtual threads with a single spring.threads.virtual.enabled=true property.

  • Virtual threads work especially well for blocking I/O, such as database queries and calls to external services.

  • Structured concurrency can simplify parallel I/O when a request needs data from multiple sources.

  • Benchmark your workload before switching. Virtual threads help with concurrency and I/O, but they are not a replacement for optimizing CPU-heavy code.

This spring boot java 21 tutorial shows configuration, a working controller, and when virtual threads beat platform threads.

Prerequisites: Java basics and Spring Boot fundamentals. Complete Java Lesson 1 if you are new to Java.

Estimated time: 60–75 minutes. Difficulty: Intermediate.

What Are Virtual Threads?

Virtual threads (Project Loom, finalized in Java 21) are lightweight threads managed by the JVM rather than the operating system. A single platform thread can run thousands of virtual threads, making them ideal for I/O-bound workloads like HTTP clients, database queries, and file reads.

Diagram comparing Java platform threads one-to-one with OS threads versus many virtual threads sharing carrier threads
Platform threads vs Java 21 virtual threads — memory and scalability comparison

When to Use Virtual Threads in Spring Boot

Use virtual threads Stick with platform threads
REST APIs with blocking JDBC/JPA CPU-heavy computation (image processing, ML inference)
Microservices calling external HTTP APIs Code using synchronized blocks on hot paths (pinning)
High concurrency, moderate latency tolerance Legacy libraries not yet virtual-thread friendly

What You Need

  • JDK 21 (LTS) — download from Adoptium
  • Spring Boot 3.2+ (3.3 recommended)
  • Maven 3.9+ or Gradle 8+
  • Optional: hey or Apache JMeter for load testing

Step 1: Create the Spring Boot 3 Project

At start.spring.io, select Java 21, Spring Boot 3.3.x, and add Spring Web. Name the project virtual-threads-demo.

<properties>
  <java.version>21</java.version>
</properties>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
Spring Initializr showing Java 21 and Spring Boot 3.3 selected for new REST API project
Spring Initializr — Java 21 + Spring Boot 3.3

Step 2: Enable Virtual Threads

Add one line to application.properties:

spring.threads.virtual.enabled=true

Spring Boot 3.2+ configures Tomcat to use virtual threads for request handling automatically. No custom Executor bean is required for standard MVC controllers.

application.properties file with spring.threads.virtual.enabled=true highlighted in IDE
Enable virtual threads with a single Spring Boot property

Step 3: Build a REST API with Blocking I/O

Create a controller that simulates I/O latency — the classic use case for virtual threads:

@RestController
@RequestMapping("/api/products")
public class ProductController {

    @GetMapping("/{id}")
    public Product getProduct(@PathVariable Long id) throws InterruptedException {
        // Simulate blocking database or HTTP call
        Thread.sleep(200);
        return new Product(id, "Widget " + id, 29.99);
    }

    @GetMapping
    public List<Product> listProducts() throws InterruptedException {
        Thread.sleep(100);
        return List.of(
            new Product(1L, "Widget 1", 19.99),
            new Product(2L, "Widget 2", 29.99)
        );
    }

    record Product(Long id, String name, double price) {}
}

Run the app:

mvn spring-boot:run
curl http://localhost:8080/api/products/1
Terminal curl command returning JSON product response from Spring Boot REST API
REST API response — each request runs on a virtual thread

Step 4: Structured Concurrency Example

Java 21’s structured concurrency lets you fan out parallel I/O safely:

@GetMapping("/aggregate/{id}")
public AggregatedProduct aggregate(@PathVariable Long id) throws Exception {
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
        Subtask<Product> product = scope.fork(() -> fetchProduct(id));
        Subtask<Reviews> reviews = scope.fork(() -> fetchReviews(id));

        scope.join();
        scope.throwIfFailed();

        return new AggregatedProduct(product.get(), reviews.get());
    }
}

This pattern works well in Spring services that call multiple downstream APIs — common in microservices like the InventoryMS Spring Boot API.

Step 5: Benchmark Platform vs Virtual Threads

Load-test with hey (install via Homebrew or GitHub releases):

# With virtual threads enabled (default after step 2)
hey -n 5000 -c 200 http://localhost:8080/api/products/1

# Disable virtual threads, restart, and compare:
# spring.threads.virtual.enabled=false
hey -n 5000 -c 200 http://localhost:8080/api/products/1
hey load test results comparing requests per second with virtual threads enabled versus disabled
Virtual threads typically sustain higher concurrency under I/O-bound load

Frequently Asked Questions

Do I need to change my Spring Boot controller code for virtual threads?
No. Enable spring.threads.virtual.enabled=true and existing blocking MVC controllers run on virtual threads automatically. Avoid wrapping every call in CompletableFuture unless you have a specific reason.

Are virtual threads available in Java 17?
Virtual threads were preview/incubator in Java 19–20 and finalized in Java 21 LTS. Spring Boot 3.2+ requires Java 17 minimum but virtual thread support targets Java 21.

What is thread pinning and should I worry?
Pinning occurs when synchronized blocks or native code prevent a virtual thread from unmounting. Most Spring/JDBC drivers are pinning-safe in 2026, but profile CPU-bound synchronized code if throughput plateaus.

Do virtual threads work with Spring WebFlux?
WebFlux is already reactive/non-blocking. Virtual threads target the Servlet stack (Spring MVC + Tomcat). Use one model per service — don’t mix unnecessarily.

Can I use virtual threads with @Async?
Yes. Define a TaskExecutor bean using Executors.newVirtualThreadPerTaskExecutor() and reference it in @Async("virtualThreadExecutor").

How does this relate to the RAG chatbot tutorial?
AI chat endpoints are I/O-bound (embedding + LLM calls). Virtual threads let a RAG service handle many concurrent users — see the Spring Boot RAG tutorial.

Final Thoughts

Java 21 virtual threads make high-concurrency Spring Boot applications much easier to build without forcing developers to abandon the familiar servlet programming model. For applications that spend significant time waiting on databases, HTTP services, or other I/O operations, they can provide a practical way to handle more concurrent requests with less thread-management overhead.

The key is understanding where virtual threads help. They are designed for workloads that spend time waiting, not for making CPU-intensive operations faster. That is why benchmarking your actual application matters more than assuming virtual threads will automatically improve performance.

Start with the simple Spring Boot configuration, test it against a realistic workload, and measure the results. From there, you can decide whether virtual threads are the right fit for your REST API and downstream services.

Next Steps

Master modern Java? Join Alkademy for live Java 21 and Spring Boot courses with real-world projects.



Kindson Munonye

Kindson Munonye is a software engineer and technical author covering machine learning, statistics, REST APIs, Python, and software engineering. He publishes free tutorials on The Genius Blog and live classes on Alkademy. GitHub · LinkedIn · About · Alkademy

View all posts by Kindson Munonye →
0 0 votes
Article Rating
Subscribe
Notify of
guest

1 Comment
Oldest
Newest Most Voted
trackback

[…] Spring Boot 3 + Java 21 Virtual Threads — scale concurrent chat requests […]