Skip to main content
Advertisement

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);
}

tip

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.

Advertisement