Learn to create asynchronous methods in the Spring framework with the help of @Async and @EnableAsync annotations that use a thread pool on top of Java ExecutorService framework.
1. Setting Up @EnableAsync and @Async
Spring comes with @EnableAsync annotation and can be applied to a @Configuration class for asynchronous behavior. The @EnableAsync annotation will look for methods marked with @Async annotation and run them in background thread pools.
The @Async annotated methods are executed in a separate thread and return CompletableFuture to hold the result of an asynchronous computation.
To enable async configuration in spring, follow these steps:
Create a thread pool to run the tasks asynchronously.
Annotate the method with @Async that shall run asynchronously. The method must be public and may or may not return a value. The return value should be wrapped in a Future interface implementation if it returns a value.
2. Spring REST Controller Example with Async Tasks
In this demo, we will create a REST API that fetches data from three remote services asynchronously. When responses from all three services are available, we will aggregate the responses.
Invoke EmployeeName API
Invoke EmployeeAddress API
Invoke EmployeePhone API
Wait for responses from the above services
Aggregate all three API responses and build the final response to send back to the client
2.1. Remote REST APIs to be Consumed Asynchronously
The following are remote APIs that the async REST controller must consume before aggregating the data and returning the result. The following class is only for demo purposes. The actual API may have different methods.
These service methods will pull the data from the remote APIs or a database and must run in parallel in separate threads to speed up the process.
Application code that invokes the client API
@ServiceclassAsyncService{privatestaticfinalLogger log =LoggerFactory.getLogger(AsyncService.class);@Async("asyncExecutor")publicCompletableFuture<String>getEmployeeName()throwsInterruptedException{
log.info("Fetching Employee Name using RestClient/WebClient API...");Thread.sleep(1000);returnCompletableFuture.completedFuture("John Doe");}@Async("asyncExecutor")publicCompletableFuture<String>getEmployeeAddress()throwsInterruptedException{
log.info("Fetching Employee Address using RestClient/WebClient API...");Thread.sleep(1000);returnCompletableFuture.completedFuture("123 Main St, Cityville");}@Async("asyncExecutor")publicCompletableFuture<String>getEmployeePhone()throwsInterruptedException{
log.info("Fetching Employee Phone using RestClient/WebClient API...");Thread.sleep(1000);returnCompletableFuture.completedFuture("+123456789");}}
2.3. Invoking Async Methods and Aggregating Results
This is the main API that calls the async methods, consumes and aggregates their responses, and returns to the client.
When a method return type is a Future, the Future.get() method will throw the exception and we should use try-catch block to catch and handle the exception before aggregating the results.
The problem is if the async method does not return any value then it is hard to know if an exception occurred while the method was executing. We can use AsyncUncaughtExceptionHandler implementation for catching and handling such exceptions.
A fun-loving family man, passionate about computers and problem-solving, with over 15 years of experience in Java and related technologies.
An avid Sci-Fi movie enthusiast and a fan of Christopher Nolan and Quentin Tarantino.
Comments