Asynchronous calls in Spring Boot using @Async annotation

Sandesh Harne
Globant
Published in
4 min readJun 29, 2021

This article is about how asynchronous behaviour can be achieved in spring boot. But first of all, let’s see the difference between synchronous and asynchronous.

  • Synchronous Programming: In synchronous programming, tasks are performed one at a time and only when one is completed the next is unblocked.
  • Asynchronous Programming: In asynchronous programming, multiple tasks can be executed simultaneously. You can move to another task before the previous one finishes.
Figure 1.1

In spring boot, we can achieve asynchronous behaviour using @Async annotation. But just @Async annotation will not work. For that, you need to understand how @Async internally works.

Prerequisite:

It is mandatory to enable async support by annotating the main application class or any direct or indirect async method caller class with @EnableAsync annotation. By default mode is Proxy and another one is AspectJ. In this post, we are going to discuss Proxy mode. Proxy mode allows for the interception of the call through proxy only. Never call the async method from the same class where it is defined, it will not work.

How does @Async annotation work?

First, annotate the method with @Async. When you annotate a method with @Async annotation, it creates a proxy for that object based on “proxyTargetClass” property.

When spring executes this method, by default it will be searching for associated thread pool definition. Either a unique spring framework TaskExecutor bean in the context or Executor bean named “taskExecutor”. If neither of these two is resolvable, default it will use spring framework SimpleAsyncTaskExecutor to process async method execution.

AsyncService.java

package com.example.demo.service;public interface AsyncService {
void asyncMethod() throws InterruptedException;
}

AsyncServiceImpl.java

package com.example.demo.service;import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class AsyncServiceImpl implements AsyncService {
@Async
@Override
public void asyncMethod() throws InterruptedException {
Thread.sleep(3000);
System.out.println("Calling other service..");
System.out.println("Thread: " +
Thread.currentThread().getName());
}
}

AsyncServiceImpl should be a spring managed bean. Also, your async method must be public so that it can be proxied. In the terms of the async method signature, any parameter types are supported. However, the return type is constrained to either void or Future.

AsyncController.java

package com.example.demo.controller;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.demo.service.AsyncService;@RestController
@EnableAsync
public class AsyncController {
@Autowired
AsyncService asyncService;
@GetMapping("/async")
public String asyncCallerMethod() throws InterruptedException {
long start = System.currentTimeMillis();
asyncService.asyncMethod();
String response = "task completes in :" +
(start - System.currentTimeMillis()) + "milliseconds";
return response;
}
}

Here, I have annotated the class with @EnableAsync. When you run the service and hit the endpoint, asyncMethod() will be executed by another thread created by the default task executor. The main thread need not wait to finish the async method execution.

You can customize your own task executor. We will see it while discussing how to handle exception occurs in an async method.

Exception handling in async method:

To handle exception in the async method we need to configure AsyncUncaughtExceptionHandler as shown below.

AsynConfiguration.java

package com.example.demo.config;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.AsyncConfigurerSupport;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@Configuration
public class AsynConfiguration extends AsyncConfigurerSupport {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new
ThreadPoolTaskExecutor();
executor.setCorePoolSize(3);
executor.setMaxPoolSize(4);
executor.setThreadNamePrefix("asyn-task-thread-");
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler
getAsyncUncaughtExceptionHandler() {
return new AsyncUncaughtExceptionHandler() {

@Override
public void handleUncaughtException(Throwable ex,
Method method, Object... params) {
System.out.println("Exception: " + ex.getMessage());
System.out.println("Method Name: " + method.getName());
ex.printStackTrace();
}
};
}
}

AsyncServiceImpl.java

package com.example.demo.service;import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class AsyncServiceImpl implements AsyncService {

@Async
@Override
public void asyncMethod() throws InterruptedException {
int a = 1;
int b = 0;
System.out.println(a/b);
}
}

Run the application and hit the endpoint http://localhost:8080/async The browser will show the response string return in AsyncController class and the Arithmetic Exception(divide by zero) will be logged in the console.

Console logs:

Figure 1.2

Conclusion:

In this article, we have seen one of the ways of achieving asynchronous behaviour in spring boot using @Async annotation and exception handling in the async method.

If you found this useful, or have any suggestions, please let me know in the comments. Also, feel free to reach out to me on LinkedIn.

--

--