Asynchronous Processing
Virtual Threads
Spring Async annotation
Java Concurrency
Multithreading

Asynchronous processing using virtual threads and Async annotation

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Virtual threads and Spring's @Async are often discussed together, but they solve different layers of the same problem. Virtual threads are a Java runtime feature for scaling blocking work cheaply, while @Async is a Spring mechanism for offloading a method call to an executor.

Virtual Threads

What are Virtual Threads?

Virtual threads, introduced as a preview feature in Java 19, build upon the traditional thread model by providing a much lighter and more resource-efficient thread construct. Unlike platform threads that map directly to OS threads, virtual threads are managed by the Java runtime, offering high scalability with minimal overhead.

Advantages of Virtual Threads

  • Efficiency: Virtual threads drastically reduce the memory footprint since they are not directly mapped to OS threads.
  • Scalability: They enable creating thousands or even millions of concurrent threads, which is not feasible with traditional threads.
  • Simplified Concurrency: Virtual threads minimize the complexity associated with thread pools.

Example Usage

Below is an example demonstrating how virtual threads can be utilized for asynchronous processing:

Why they are not direct alternatives

@Async answers the question, "should this method run asynchronously?" Virtual threads answer, "what kind of thread should the executor use?" That means one can sit on top of the other. A Spring method marked with @Async still needs an executor, and that executor can use either platform threads or virtual threads.

For I/O-heavy services, virtual threads are attractive because the code can stay simple and blocking while the runtime handles large amounts of waiting work efficiently.

java
1import java.util.concurrent.ExecutorService;
2import java.util.concurrent.Executors;
3
4public class VirtualThreadDemo {
5    public static void main(String[] args) throws Exception {
6        try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
7            for (int i = 0; i < 5; i++) {
8                int id = i;
9                executor.submit(() -> {
10                    Thread.sleep(500);
11                    System.out.println("Task " + id + " on " + Thread.currentThread());
12                    return null;
13                });
14            }
15        }
16    }
17}

Spring @Async in context

In a Spring application, @Async is useful when you want asynchronous execution to be declarative. A service method can return CompletableFuture, and callers do not have to manage thread submission directly.

That convenience does not remove executor choice. If the underlying executor is a poor fit for the workload, @Async alone will not create good concurrency behavior. The strongest design is often to keep @Async as the bean boundary and back it with virtual threads when the work is mostly blocking.

One important Spring caveat is proxying. A method calling another @Async method on the same instance usually does not trigger asynchronous execution because the call never passes through the Spring proxy.

That detail matters in service design. If asynchronous behavior is part of the contract, structure the call path so the invocation really crosses the proxy boundary instead of assuming the annotation alone guarantees off-thread execution.

Common Pitfalls

  • Treating virtual threads and @Async as if they are the same feature.
  • Assuming virtual threads automatically improve CPU-bound tasks.
  • Using @Async without understanding that it still depends on an executor underneath.
  • Forgetting that self-invocation usually bypasses Spring's proxy-based @Async behavior.
  • Reaching for more complicated concurrency models when simple blocking code plus virtual threads would be enough.

Summary

  • Virtual threads are a Java thread model; @Async is a Spring method-dispatch mechanism.
  • '@Async decides that work runs off the caller thread, while the executor decides how it runs.'
  • Virtual threads are especially useful for large numbers of blocking I/O tasks.
  • Spring applications can combine @Async with virtual-thread-backed executors effectively.
  • Neither feature replaces the need for clear workload analysis and sensible executor design.

Course illustration
Course illustration

All Rights Reserved.