@Async annotation
About
The @Async
annotation in Spring allows to run methods asynchronously in a separate thread, rather than executing them in the main thread. It’s commonly used to improve application performance by offloading long-running tasks or non-blocking operations.
Some Points to Note
Calling @Async
Methods within the Same Class
If we call an @Async
method from within the same class, it won’t work as expected because the AOP proxy mechanism Spring uses will not intercept the call. A workaround is to call the async method from a different bean or through self-injection.
Handling Return Values Properly
Since @Async
methods return immediately, we should be careful when expecting a return value. Ensure that the calling code handles Future
or CompletableFuture
appropriately, especially for exception handling and timeout scenarios.
5.3. Overloading the Thread Pool
Ensure that the thread pool configuration matches the application's load. An improperly configured thread pool can either lead to resource exhaustion (too many threads) or poor performance (too few threads).
How to use it?
1. Basic Setup
To enable asynchronous processing, we need to configure our Spring application.
Enable Asynchronous Processing:
Use
@Async
in Methods:
Calling Asynchronous Methods:
When we call asyncMethod()
, it will run in a different thread than the caller thread.
2. Return Type of Asynchronous Methods
Void Return Type: If we don't need to return anything, the method can simply have a
void
return type.Future Return Type: If we need to return a result, can use
java.util.concurrent.Future
.CompletableFuture: As of Java 8,
CompletableFuture
can be used as a more flexible alternative toFuture
.
3. Using Custom Thread Pools
By default, Spring uses a SimpleAsyncTaskExecutor
, but in production, we may want to customize the thread pool to control the number of threads, queue size, etc.
Define a Custom Executor:
Specify the Executor: We can specify which executor to use for a particular method.
4. Exception Handling in Async Methods
Unhandled exceptions in asynchronous methods are not propagated to the calling thread. We need to handle exceptions within the method or provide a custom AsyncUncaughtExceptionHandler
.
Custom Async Exception Handler:
5. Chaining Asynchronous Calls
Using CompletableFuture
, we can chain asynchronous calls, making it easier to compose complex asynchronous workflows.
6. Integration with Spring Events
We can combine @Async
with Spring’s event-driven programming model. By using @Async
on event listeners, we can handle events asynchronously.
When to Use @Async
?
@Async
?1. Simple Asynchronous Execution:
Background Tasks: Use
@Async
when you want to offload simple, non-blocking tasks to a background thread. For example, sending an email, logging, or processing a file in the background without blocking the main thread.Fire-and-Forget: If the method doesn't need to return a result or you don't care about the outcome (like sending notifications or logging),
@Async
is a good fit.
2. Declarative Asynchronous Programming:
Spring Integration: If you are already using Spring and want a simple declarative approach to make a method asynchronous,
@Async
is an easy-to-use annotation that fits naturally within the Spring ecosystem.
3. Event-Driven Asynchrony:
Handling Events Asynchronously: When combined with Spring's event mechanism,
@Async
can make event listeners execute asynchronously, which is useful in systems where you need to decouple event processing from the main application flow.
4. Simple Configurations:
Thread Pool Management: If you need basic thread pool management but don't want to deal with the complexity of manually managing threads,
@Async
with a custom executor is a straightforward solution.
5. Exception Handling:
Custom Async Exception Handling:
@Async
allows you to define customAsyncUncaughtExceptionHandler
to handle uncaught exceptions in async methods, making it easier to handle exceptions without disrupting the main application flow.
When to Use CompletableFuture
?
CompletableFuture
?1. Complex Asynchronous Workflows:
Chaining Asynchronous Tasks: If your application requires a sequence of asynchronous tasks where each task depends on the result of the previous one,
CompletableFuture
is ideal. It allows you to chain tasks together using methods likethenApply
,thenCompose
, andthenAccept
.Combining Results from Multiple Tasks: If you need to run multiple asynchronous tasks in parallel and then combine their results,
CompletableFuture
provides methods likeallOf
,anyOf
, andjoin
to handle such scenarios.
2. Fine-Grained Control:
Custom Execution Logic:
CompletableFuture
provides more control over how tasks are executed. You can specify different executors for different tasks, handle exceptions at each step, and control how results are combined.Handling Timeouts and Delays: With
CompletableFuture
, you can handle timeouts, delays, and retries more flexibly. For instance, you can specify a timeout for a task or retry a task if it fails.
3. Non-Blocking Async Calls:
Reactive and Non-Blocking Systems: In reactive or non-blocking architectures,
CompletableFuture
is often used to handle async calls without blocking the main thread. This is especially useful in highly concurrent systems where blocking threads can lead to performance bottlenecks.
4. Combining with Other Java 8+ Features:
Streams and Parallelism:
CompletableFuture
integrates well with other Java 8+ features like Streams and parallel processing, making it easier to build complex data processing pipelines that operate asynchronously.
5. Better Control Over Exceptions:
Exception Handling:
CompletableFuture
provides mechanisms likeexceptionally
,handle
, andwhenComplete
to manage exceptions at various stages of the asynchronous workflow, offering more granular control compared to@Async
.
Impact on Thread Context When Main Thread Exits
Thread Context:
The thread context includes things like security context, transaction context, and any thread-local variables.
When we return immediately and the main thread handling the HTTP request exits, the thread context associated with that main thread is no longer available.
Impact with
@Async
:Spring will execute the
@Async
method in a different thread. The new thread will not have the same context as the original main thread unless we explicitly propagate the context (e.g., usingDelegatingSecurityContextAsyncTaskExecutor
for security context).If our asynchronous task relies on the main thread's context (e.g., security context or transaction management), we need to ensure that this context is either propagated or re-established in the new thread.
Impact with
CompletableFuture
:Similar to
@Async
, when usingCompletableFuture
, the task is executed in a different thread (unless we explicitly provide an executor with the same context).Any context tied to the original request thread will not be present in the thread executing the
CompletableFuture
. This means if our task relies on the original thread's context, we may need to manually pass the necessary information or use context-aware executors.
Example - User Registration and Notification Service
Imagine we are building a user registration system for an e-commerce platform. When a user registers, the system performs the following actions:
Save the User Details in the database.
Send a Welcome Email to the user.
Notify Admins of the new user registration.
Generate a Welcome Gift Voucher for the new user.
Some of these tasks, like sending an email or notifying admins, can be done asynchronously to improve the overall performance of the application.
Last updated
Was this helpful?