Clean • Professional
Java 21 introduces Virtual Threads, a lightweight concurrency model that allows running thousands—or even millions—of concurrent tasks efficiently, without the overhead of traditional OS threads. This is a major shift for high-throughput microservices, simplifying thread management while maintaining performance.
Virtual Threads, introduced in Java 21 via Project Loom, are lightweight threads managed by the JVM rather than the operating system. Unlike traditional OS threads, virtual threads are extremely cheap to create and allow applications to handle millions of concurrent tasks with minimal memory usage.
Key Features:
| Feature | Platform (OS) Thread | Virtual Thread |
|---|---|---|
| Creation Cost | High (~1MB stack) | Very low (~1KB stack) |
| Blocking | Expensive, blocks OS thread | Cheap, doesn’t block OS thread |
| Ideal For | CPU-bound tasks | I/O-bound tasks |
| Scalability | Hundreds to thousands | Hundreds of thousands+ |
Microservices often make multiple I/O-bound calls per request, such as:
Traditional threads:
Virtual threads:
Single virtual thread:
Thread vt = Thread.ofVirtual().start(() -> {
System.out.println("Running in virtual thread: " + Thread.currentThread());
});
vt.join();
Executor-based (preferred for multiple tasks):
ExecutorServiceexecutor= Executors.newVirtualThreadPerTaskExecutor();
executor.submit(() -> {
System.out.println("Processing request in virtual thread: " + Thread.currentThread());
});
executor.shutdown();
With Java 21 and Spring Boot, virtual threads can be used in REST controllers, service layers, @Async methods, and CompletableFuture for lightweight, scalable concurrency.
Configure Executor:
@Bean(name = "virtualThreadExecutor")
public Executor virtualThreadExecutor() {
return Executors.newVirtualThreadPerTaskExecutor();
}
Example with @Async:
@Async("virtualThreadExecutor")
public CompletableFuture<String> fetchData() {
String data = restTemplate.getForObject("<http://service/api>", String.class);
return CompletableFuture.completedFuture(data);
}
Example with CompletableFuture:
CompletableFuture.supplyAsync(
() -> restTemplate.getForObject("<http://service/api>", String.class),
virtualThreadExecutor()
);
Example REST Controller Using Virtual Threads:
@RestController
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@GetMapping("/order/{id}")
public CompletableFuture<Order> getOrder(@PathVariable Long id) {
return CompletableFuture.supplyAsync(
() -> orderService.getOrder(id),
Executors.newVirtualThreadPerTaskExecutor()
);
}
}
@Async, and CompletableFutureScenario: Aggregating user info, payment info, and inventory info for an order summary in a microservices architecture.
Flow:
Client → API Gateway → Virtual Thread per Request → Multiple I/O Calls → Response
Virtual Threads introduced in Java 21 transform how microservices handle concurrency:
CompletableFuture, and @Async.