Master Asynchronous Programming in Spring Boot 3 with @Async and CompletableFuture
This article demonstrates how to enable and use Spring Boot 3's @Async annotation together with Java's CompletableFuture to create custom thread pools, run multiple asynchronous REST calls concurrently, aggregate their results, and handle exceptions, providing a complete backend solution for high‑performance APIs.
@Async and CompletableFuture are powerful tools for asynchronous processing in Spring Boot 3.2.5.
@Async is a Spring annotation that marks a method to be executed asynchronously in a thread pool, improving performance and responsiveness.
CompletableFuture, introduced in Java 8, represents a result of a computation that may not have completed yet and offers a rich API for callbacks, composition, and error handling.
Combining @Async with CompletableFuture enables efficient asynchronous task handling.
1. Enable Async Support
Apply @EnableAsync on a configuration class and define a custom Executor bean.
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "asyncExecutor")
public Executor asyncExecutor() {
int core = Runtime.getRuntime().availableProcessors();
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(core);
executor.setMaxPoolSize(core);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("PackAsync-");
executor.initialize();
return executor;
}
}You may also rely on Spring's default executor.
2. Create Async Tasks
Annotate public methods with @Async("asyncExecutor") and return CompletableFuture<T>.
@Async("asyncExecutor")
public CompletableFuture<EmployeeNames> task() {
// TODO
}Multiple async tasks can run concurrently and be combined with CompletableFuture.allOf(...).join().
CompletableFuture.allOf(asyncMethodOne, asyncMethodTwo, asyncMethodThree).join();3. Call Async Tasks from a RestController
Define three REST endpoints that provide data.
@RestController
public class EmployeeController {
@GetMapping("/addresses")
public EmployeeAddresses addresses() { /* TODO */ }
@GetMapping("/phones")
public EmployeePhone phones() { /* TODO */ }
@GetMapping("/names")
public EmployeeNames names() { /* TODO */ }
}In a service, use RestTemplate to fetch each endpoint asynchronously.
@Service
public class AsyncService {
private final RestTemplate restTemplate;
public AsyncService(RestTemplate restTemplate) { this.restTemplate = restTemplate; }
@Async("asyncExecutor")
public CompletableFuture<EmployeeNames> names() {
EmployeeNames data = restTemplate.getForObject("http://localhost:8080/names", EmployeeNames.class);
return CompletableFuture.completedFuture(data);
}
// similar methods for addresses() and phones()
}Expose a combined API that waits for all futures and returns a DTO.
@RestController
public class AsyncController {
private final AsyncService asyncService;
public AsyncController(AsyncService service) { this.asyncService = service; }
@GetMapping("/profile/infos")
public EmployeeDTO infos() throws Exception {
CompletableFuture<EmployeeAddresses> addresses = asyncService.addresses();
CompletableFuture<EmployeeNames> names = asyncService.names();
CompletableFuture<EmployeePhone> phones = asyncService.phones();
CompletableFuture.allOf(addresses, names, phones).join();
return new EmployeeDTO(addresses.get(), names.get(), phones.get());
}
}The total response time equals the longest individual call, greatly improving the endpoint's performance.
4. Exception Handling
When async methods return Future, handle exceptions with try‑catch after get(). For void async methods, implement AsyncUncaughtExceptionHandler to capture uncaught exceptions.
@Configuration
public class AsyncConfig implements AsyncConfigurer {
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new AsyncExceptionHandler();
}
public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
private final Logger logger = LoggerFactory.getLogger(AsyncExceptionHandler.class);
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
logger.error("Unexpected asynchronous exception at: " + method.getDeclaringClass().getName() + "." + method.getName(), ex);
}
}
}Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
