2.7 Asynchronous Processing (@Async)
For work that does not need to block the request thread (e.g. email, external API calls, notifications), use @Async to run it on a separate thread and release the request thread quickly.
Reference: Spring Boot 3.2.x
1. @EnableAsync and Basic Usage
@SpringBootApplication
@EnableAsync
public class Application { ... }
@Service
public class NotificationService {
@Async
public void sendEmail(String to, String subject, String body) {
// mail sending logic — runs on a separate thread
mailSender.send(to, subject, body);
}
}
- Methods annotated with @Async run on a different thread.
- Calling
this.sendEmail(...)within the same bean bypasses the proxy and does not run asynchronously. The call must come from another bean.
2. Thread Pool
Configure a thread pool instead of the default SimpleAsyncTaskExecutor (creates a new thread per task) for production use.
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("async-");
executor.initialize();
return executor;
}
}
Use @Async("customExecutor") to target a specific executor bean for individual methods.
3. Return Value — CompletableFuture
Return CompletableFuture<T> to use the async result later.
@Async
public CompletableFuture<OrderResult> createOrderAsync(OrderRequest request) {
OrderResult result = orderService.create(request);
return CompletableFuture.completedFuture(result);
}
The caller can use .get() or .thenAccept(...) etc. to consume the result.
4. Exception Handling
Exceptions from @Async methods do not propagate to the caller. Configure AsyncUncaughtExceptionHandler, or if the return type is Future/CompletableFuture, exceptions are wrapped in ExecutionException when calling .get().
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (ex, method, params) ->
log.error("Async error @{} {}", method.getName(), params, ex);
}
Only offload heavy tasks to @Async, and tune thread pool size and queue capacity to match your load. Async methods that call DB or external APIs are often combined with timeout and Retry.