Spring Asynchronous Execution using @Async

Spring Async

In this tutorial I will discuss about the asynchronous execution support in Spring using the @Async annotation. There are cases where Spring framework’s @Async is necessary to execute piece of code asynchronous. An example would be while sending a (JMS) message from one system to another system. The advantage is that the user does not have to wait for the response while the message is being send.

@Async annotation on a method of a bean will execute in a separate thread i.e. the caller does not need to wait for the completion of the called method.

Enabling Async Support

The @EnableAsync annotation switches on Spring’s ability to run @Async methods in a background thread pool. For Enabling asynchronous processing with Java configuration got by simply adding the @EnableAsync to a configuration class

@EnableAsync
@Configuration
public class SpringAsyncConfigurer implements AsyncConfigurer {...}

Limitations of @Async

  • It is applicable only to the public methods
  • Calling the @Async method within the same class would not work

Prerequisites

Maven 3.6.3/3.8.5, Gradle 6.1.1 – 6.8.3, Spring Boot 2.2.5 – 2.4.4/3.1.3, Java 8/12/19

Project Setup

First step is to create a maven based project in your favorite IDE or tool. The name of the project is spring-async-example.

For the maven based project you can use the following pom.xml file.

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.roytuts</groupId>
	<artifactId>spring-async-example</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>19</maven.compiler.source>
		<maven.compiler.target>19</maven.compiler.target>
	</properties>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.1.3</version>
	</parent>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>
					org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

Spring Async Configuration

Create below configuration class for @Async support for the application.

I have defined Thread pool executor with the custom values, such as, number of core pool, queue capacity and maximum pool size.

@EnableAsync
@Configuration
public class SpringAsyncConfigurer implements AsyncConfigurer {

	@Override
	public Executor getAsyncExecutor() {
		ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
		executor.setCorePoolSize(10);
		executor.setMaxPoolSize(25);
		executor.setQueueCapacity(100);
		executor.initialize();
		return executor;
	}

	@Override
	public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
		return new CustomAsyncExceptionHandler();
	}

}

ThreadPoolTaskExecutor – the main idea is that when a task is submitted, the executor will first try to use a free thread if the number of active threads is currently less than the core size. If the core size has been reached, then the task will be added to the queue as long as its capacity has not yet been reached. Only then, if the queue’s capacity has been reached, will the executor create a new thread beyond the core size. If the maximum size has also been reached, then the executor will reject the task.

I have created a custom async exception handler by implementing AsyncUncaughtExceptionHandler interface to invoke handleUncaughtException() method when there is any uncaught asynchronous exceptions.

public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {

	@Override
	public void handleUncaughtException(Throwable throwable, Method method, Object... obj) {
		System.out.println("Exception message - " + throwable.getMessage());
		System.out.println("Method name - " + method.getName());
		for (Object param : obj) {
			System.out.println("Parameter value - " + param);
		}
	}

}

Spring REST controller

Create Spring REST controller class. The below REST controller has two endpoints, one is with http GET request and another is with http POST request.

@RestController
public class GreetingRestController {

	@Autowired
	private GreetingService greetingService;

	@GetMapping("greet/{name}")
	public ResponseEntity<String> getGreetingMsg(@PathVariable String name)
			throws InterruptedException, ExecutionException {

		String msg = greetingService.getGreetingMsg(name).get();

		return new ResponseEntity<String>(msg, HttpStatus.OK);
	}

	@PostMapping("log")
	public ResponseEntity<Void> logMsg() throws InterruptedException, ExecutionException {

		greetingService.logMsg();

		return new ResponseEntity<Void>(HttpStatus.OK);
	}

}

Spring Service

When a method return type is a Future, exception handling is easy – Future.get() method will throw the exception. But, if the return type of a method is void, exceptions will not be propagated to the calling thread. Hence you need to add extra configurations to handle exceptions.

@Service
public class GreetingService {

	@Async
	public Future<String> getGreetingMsg(final String name) throws InterruptedException {
		System.out.println("Execute method asynchronously. " + Thread.currentThread().getName());
		Thread.sleep(1000);
		return new AsyncResult<String>("Hello! Good Morning, " + name);
	}

	@Async
	public void logMsg() {
		System.out.println("Execute method asynchronously. " + Thread.currentThread().getName());
		System.out.println("Today's date: " + new Date());
	}

}

If you want to get response in future then you can apply @Async to a method with return type – by wrapping the actual return in a Future. Spring provides a AsyncResult class which implements Future. This can be used to track the result of asynchronous method execution.

In spring boot version 3, the AsyncResult has been deprecated, so you need to use CompletableFuture. For spring boot version 3, you can replace the getGreetingMsg() with the following code snippets:

@Async
public Future<String> getGreetingMsg(final String name) throws InterruptedException {
	System.out.println("Execute method asynchronously. " + Thread.currentThread().getName());
	Thread.sleep(1000);
	return CompletableFuture.completedFuture("Hello! Good Morning, " + name);
}

Testing the Async Example

Try to open the same URL in different browser windows, you will see different threads executing the trasks.

RequestGET http://localhost:8080/greet/Soumitra

ResponseHello! Good Morning, Soumitra

spring asynchronous

RequestPOST http://localhost:8080/log

Response200 OK

Console output

Execute method asynchronously. ThreadPoolTaskExecutor-1
Execute method asynchronously. ThreadPoolTaskExecutor-2

That’s all about how to use @Async in Spring application.

Source Code

Download

Leave a Reply

Your email address will not be published. Required fields are marked *