We can achieve asynchronous behavior in spring boot application using two annotations like :
- @EnableAsync
- @Async.
This annotation and can be applied on application classes for asynchronous behavior. This annotation will look for methods marked with @Async annotation and run in background thread pools.
The @Async annotated methods can return CompletableFuture to hold the result of an asynchronous computation.
CompletableFuture is used for asynchronous programming in Java. Asynchronous programming means writing non-blocking code by running a task on a separate thread than the main application thread and notifying the main thread about its progress, completion or failure.
It runs a task on a separate thread than the main application thread and notifies the main thread about its progress, completion or failure.
In this way, the main thread does not block or wait for the completion of the task. Other tasks execute in parallel. Parallelism improves the performance of the program.
Having this kind of parallelism greatly improves the performance of your programs.
A CompletableFuture is a class in Java. It belongs to java.util.cocurrent package. It implements CompletionStage and Future interface.
Now configure to achieve asynchronous behavior in spring boot restful web application by using follow steps:
@Configuration
@EnableAsync
public class AsyncConfig
{
@Bean(name ="taskExecutor")
public Executor taskExecutor()
{
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(2);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("Thread-");
executor.initialize();
return executor;
}
}
@Async
@Override
public CompletableFuture<List<Customer>> saveCustomers(MultipartFile file)
{
List<Customer> customers = null;
Long start = System.currentTimeMillis();
customers = customerDao.saveAllCustomer(CSVOperation.parseCSVFile(file)); // GET DATA FROM CSV AND SAVE IN DB
Long end = System.currentTimeMillis();
log.info("Completation Time : {}", (end-start));
return CompletableFuture.completedFuture(customers);
}
@Async
@Override
public CompletableFuture<List<Customer>> getCustomersUsingAsync()
{
List<Customer> customers = customerDao.getCustomers();
try { Thread.sleep(50000); }
catch (InterruptedException e) { e.printStackTrace(); } // WAIT FOR SOME SECONDS
return CompletableFuture.completedFuture(customers);
}
@PostMapping()
public ResponseEntity<Object> saveCustomers(@RequestParam("file") MultipartFile files)
{
List<Customer> customers = customerService.getCustomers();// NORMAL METHOD CALL TO GET ALL CUSTOMERS
log.debug("Customers : ",customers);
customerService.saveCustomers(files);// READ 3000 RECORDS FROM CSV AND SAVE INTO DB -> THROUGH ASYNC METHOD
return ResponseEntity.status(HttpStatus.CREATED).build();
}
@GetMapping()
public CompletableFuture<Object> getCustomers()
{
return customerService.getCustomersUsingAsync().thenApply(ResponseEntity::ok);
}
@GetMapping("/multiple")
public ResponseEntity<Object> getMultipleCustomers()
{
CompletableFuture<List<Customer>> customerList1 = customerService.getCustomersUsingAsync();
CompletableFuture<List<Customer>> customerList2 = customerService.getCustomersUsingAsync();
CompletableFuture<List<Customer>> customerList3 = customerService.getCustomersUsingAsync();
CompletableFuture.allOf(customerList1, customerList2, customerList3).join();
return ResponseEntity.status(HttpStatus.OK).build();
}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import org.springframework.web.multipart.MultipartFile;
import com.spring.async.model.Customer;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class CSVOperation
{
public static List<Customer> parseCSVFile(MultipartFile file)
{
final List<Customer> customers = new ArrayList<>();
try
{
try (final BufferedReader br = new BufferedReader(new InputStreamReader(file.getInputStream())))
{
String line;
while ((line = br.readLine()) != null)
{
final String[] data = line.split(",");
final Customer customer = new Customer();
customer.setName(data[0]);
customer.setGender(data[1]);
customer.setEmail(data[2]);
customer.setMobile(data[3]);
customers.add(customer);
}
return customers;
}
}
catch (final IOException e)
{
log.error("Failed to parse CSV file {}", e);
}
return customers;
}
}
- If you get AsyncRequestTimeoutException :
Resolved [org.springframework.web.context.request.async.AsyncRequestTimeoutException]
{
"timestamp": "2020-04-05T06:23:33.897+0000",
"status": 503,
"error": "Service Unavailable",
"message": "No message available",
"trace": "org.springframework.web.context.request.async.AsyncRequestTimeoutException\r\n\tat org.springframework.web.context.request.async.TimeoutDeferredResultProcessingInterceptor.handleTimeout(TimeoutDeferredResultProcessingInterceptor.java:42)\r\n\tat org.springframework.web.context.request.async.DeferredResultInterceptorChain.triggerAfterTimeout(DeferredResultInterceptorChain.java:79)\r\n\tat org.springframework.web.context.request.async.WebAsyncManager.lambda$startDeferredResultProcessing$5(WebAsyncManager.java:428)\r\n\tat java.util.ArrayList.forEach(Unknown Source)\r\n\tat org.springframework.web.context.request.async.StandardServletAsyncWebRequest.onTimeout(StandardServletAsyncWebRequest.java:151)\r\n\tat org.apache.catalina.core.AsyncListenerWrapper.fireOnTimeout(AsyncListenerWrapper.java:44)\r\n\tat org.apache.catalina.core.AsyncContextImpl.timeout(AsyncContextImpl.java:139)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.asyncDispatch(CoyoteAdapter.java:153)\r\n\tat org.apache.coyote.AbstractProcessor.dispatch(AbstractProcessor.java:237)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:59)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1639)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\r\n\tat java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)\r\n\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\r\n\tat java.lang.Thread.run(Unknown Source)\r\n",
"path": "/customers"
}
Cause and Soluation :
The default timeout in Tomcat is 30 seconds, so we are handel spring async request-timeout limlit or we can simple define following code in application.properties file.
spring.mvc.async.request-timeout=-1