The spring boot @Async annotation enables the class method to run asynchronous. The handling of the exception in the @Async annotation method is therefore complex. Spring boot supports the default Async Uncaught Exception handler that throws the actual exception in the console log. Spring boot allows the Async Uncaught Exception handler to be customised for errors.

This post discusses the step-by-step procedure to enable the asynchronous execution of a method using @Async annotation and exception handling using the custom Async Uncaught Exception handler.



Step 1 – Add @EnableAsync annotation to Application

For asynchronous execution, the spring boot application should be activated. The @EnableAsync annotation will allow the spring boot application in asynchronous mode. Add the @EnableAsync annotation to the class of the spring application main method. The example below shows the @EnableAsync annotation added with the main method class.

SpringBootAsyncApplication.java

package com.yawintutor;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync
public class SpringBootAsyncApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringBootAsyncApplication.class, args);
	}

}


Step 2 – Add @Async annotation to the method

The spring boot @Async annotation enables asynchronous execution of a method. The @Async annotation can be added to the method that has to be implemented asynchronously. The call method will not wait for the asynchronous method to be finished and will not wait for the asynchronous method to respond. Therefore the return data type of the @async method should be either void or future.

TestService.java

package com.yawintutor;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class TestService {

	@Async
	public void print(int index) {
		int a = 1;
		int b = 0;
		int c = 0;
		c = a/b;
		System.out.println("C value is "+c);
	}
}


Step 3 – Add Controller Class

Create a Rest controller class that invokes a service class that includes the @Async method. The controller class helps to invoke the asynchronous method. iThe example below shows a sample rest controller class that will be auto wired with a service class. The service method is invoked in one of the rest api requests.

TestController.java

package com.yawintutor;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

	@Autowired
	TestService testService;
	
	@RequestMapping(value = "/welcome", method = RequestMethod.GET)
	public String welcome() {
		for(int i=0; i<10; i++) {
			testService.print(i);
		}
		return "success";
	}

}


Step 4 – Implement AsyncConfigurer interface

Create a custom AsyncConfigurer class that includes two methods that have been implemented. The first method is used to initialise the ThreadPoolTaskExecutor. The ThreadPoolTaskExecutor specifies the size of the pool thread, the size of the queue, etc. The second method is used to configure AsyncUncaughtExceptionHandler. The default AsyncUncaughtExceptionHandler class implemented is SimpleAsyncUncaughtExceptionHandler. The AsyncUncaughtExceptionHandler custom class helps you to manage exceptions. 

SpringAsyncConfiguration.java

package com.yawintutor;

import java.lang.reflect.Method;
import java.util.concurrent.Executor;

import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@Configuration
public class SpringAsyncConfiguration implements AsyncConfigurer {
	@Override
	public Executor getAsyncExecutor() {
		ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
		executor.setCorePoolSize(1);
		executor.setMaxPoolSize(1);
		executor.setQueueCapacity(2);
		executor.initialize();
		return executor;
	}

	@Override
	public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
		return new AsyncUncaughtExceptionHandler() {

			@Override
			public void handleUncaughtException(Throwable ex, Method method, Object... params) {
				System.out.println("Throwable Exception message : " + ex.getMessage());
				System.out.println("Method name                 : " + method.getName());
				for (Object param : params) {
					System.out.println("Parameter value             : " + param);
				}
				System.out.println("stack Trace ");
				ex.printStackTrace();
			}

		};
	}
}


Step 5 – Add pom.xml file

The pom.xml file should contain the spring boot web dependency. The spring boot web dependency will add the dependent jars for the web application. The default tomcat web server will be added to the spring boot application and will run in the default port 8080. The rest api can be invoked in the tomcat server.

pom.xml

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.4.0</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.yawintutor</groupId>
	<artifactId>Spring-Application</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>SpringBootAsync</name>
	<description>Spring Boot Project</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

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

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

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

</project>


Step 6 – Run the application

The spring boot application should be started to invoke the url. Once the spring boot application is started, tomcat will run on port 8080. Open a browser and invoke the url http://localhost:8080/welcome. The browser window will show the “success” message. The exception handling will be shown in the console window.



Step 7 – Output

The console window will show the exception handling output . The console log will be shown as below.

Throwable Exception message : / by zero
Method name                 : print
Parameter value             : 0
stack Trace 
java.lang.ArithmeticException: / by zero
	at com.yawintutor.TestService.print(TestService.java:15)
	at com.yawintutor.TestService$$FastClassBySpringCGLIB$$284b10a4.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:771)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)
	at org.springframework.aop.interceptor.AsyncExecutionInterceptor.lambda$invoke$0(AsyncExecutionInterceptor.java:115)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)



Leave a Reply