Spring Boot @Async method in controller is executing synchronously
Solution 1
You are calling the @Async
method from another method in the same class. Unless you enable AspectJ proxy mode for the @EnableAsync
(and provide a weaver of course) that won't work (google "proxy self-invocation"). The easiest fix is to put the @Async
method in another @Bean
.
Solution 2
For all those who are still looking for all the steps in @Asnyc explained in a simple way, here is the answer:
Here is a simple example with @Async. Follow these steps to get @Async to work in your Spring Boot application:
Step 1: Add @EnableAsync annotation and Add TaskExecutor Bean to Application Class.
Example:
@SpringBootApplication
@EnableAsync
public class AsynchronousSpringBootApplication {
private static final Logger logger = LoggerFactory.getLogger(AsynchronousSpringBootApplication.class);
@Bean(name="processExecutor")
public TaskExecutor workExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setThreadNamePrefix("Async-");
threadPoolTaskExecutor.setCorePoolSize(3);
threadPoolTaskExecutor.setMaxPoolSize(3);
threadPoolTaskExecutor.setQueueCapacity(600);
threadPoolTaskExecutor.afterPropertiesSet();
logger.info("ThreadPoolTaskExecutor set");
return threadPoolTaskExecutor;
}
public static void main(String[] args) throws Exception {
SpringApplication.run(AsynchronousSpringBootApplication.class,args);
}
}
Step 2: Add Method which executes an Asynchronous Process
@Service
public class ProcessServiceImpl implements ProcessService {
private static final Logger logger = LoggerFactory.getLogger(ProcessServiceImpl.class);
@Async("processExecutor")
@Override
public void process() {
logger.info("Received request to process in ProcessServiceImpl.process()");
try {
Thread.sleep(15 * 1000);
logger.info("Processing complete");
}
catch (InterruptedException ie) {
logger.error("Error in ProcessServiceImpl.process(): {}", ie.getMessage());
}
}
}
Step 3: Add an API in the Controller to execute the asynchronous processing
@Autowired
private ProcessService processService;
@RequestMapping(value = "ping/async", method = RequestMethod.GET)
public ResponseEntity<Map<String, String>> async() {
processService.process();
Map<String, String> response = new HashMap<>();
response.put("message", "Request is under process");
return new ResponseEntity<>(response, HttpStatus.OK);
}
I have also written a blog and a working application on GitHub with these steps. Please check: http://softwaredevelopercentral.blogspot.com/2017/07/asynchronous-processing-async-in-spring.html
Solution 3
I had a similar issue and I had the annotations @Async and @EnableAsync in the correct beans and still the method was executing synchronously. After I checked the logs there was a warning saying that I had more than one bean of type ThreadPoolTaskExecutor and none of them called taskExecutor So...
@Bean(name="taskExecutor")
public ThreadPoolTaskExecutor defaultTaskExecutor() {
ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
//Thread pool configuration
//...
return pool;
}
See http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.html for the configuration available for the thread pool.
Solution 4
As code sample for @dave-syer answer:
This works asynchronously:
private void longRunning() {
try {
log.info("wait 3 seconds");
Thread.sleep(3000);
} catch (InterruptedException e1) {
}
log.info("done");
}
@Async
@Override
public void doWork() {
longRunning();
}
But this doesn't:
@Async
private void longRunning() {
try {
log.info("wait 3 seconds");
Thread.sleep(3000);
} catch (InterruptedException e1) {
}
log.info("done");
}
@Override
public void doWork() {
longRunning();
}
Solution 5
I used spring-boot main class to define Asynchronous configuration. The @EnableAsync
annotation enables Spring’s ability to run @Async
methods in a background thread pool. This class also customizes the Executor by defining a new bean. Here, the method is named taskExecutor()
, since this is the specific method name searched by Spring.
Spring-Boot-Application.class
@SpringBootApplication
@EnableAsync
public class AsyncApplication {
@Value("${config.threadpool.corepool.size}")
private Integer corePoolSize;
@Value("${config.threadpool.maxpool.size}")
private Integer maxPoolSize;
public static void main(String[] args) {
SpringApplication.run(AsyncApplication.class, args);
}
//name of below method should not be changed.
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
//other proeprties to be set here
executor.setThreadNamePrefix("ASYNC-");
executor.initialize();
return executor;
}
}
In Implementation, use @Async
at method level to make the method Asynchronous. Methods need to be public to use @Async
. Also, @Async
annotated method calling @Async
method will not work.
Sample implementation for reference below -
@Async
public void updateData(String userId) throws ApplicationException {
logger.info("Updating details for User with {}", userId);
//your code goes here...
}
The configuration properties are defined in application.properties
file
#Core Pool Size for Async
config.threadpool.corepool.size=100
#Max Pool Size for Async
config.threadpool.maxpool.size=400
For Rules on how to define the pool, please refer to rules-of-a-threadpool-executor
Related videos on Youtube
Web User
Updated on August 11, 2020Comments
-
Web User almost 4 years
My [basic] Spring Boot application accepts a request from the browser, sent via
jQuery.get()
and is supposed to immediately receive a response - such as "your request has been queued". To accomplish this, I wrote a controller:@Controller public class DoSomeWorkController { @Autowired private final DoWorkService workService; @RequestMapping("/doSomeWork") @ResponseBody public String doSomeWork() { workService.doWork(); // time consuming operation return "Your request has been queued."; } }
The
DoWorkServiceImpl
class implements aDoWorkService
interface and is really simple. It has a single method to perform a time consuming task. I don't need anything returned from this service call, as an email will be delivered at the end of the work, both for failure or success scenarios. So it would effectively look like:@Service public class DoWorkServiceImpl implements DoWorkService { @Async("workExecutor") @Override public void doWork() { try { Thread.sleep(10 * 1000); System.out.println("completed work, sent email"); } catch (InterruptedException ie) { System.err.println(ie.getMessage()); } } }
I thought this would work, but the browser's Ajax request waited for 10 seconds before returning the response. So the controller mapped method is calling the internal method annotated with
@Async
synchronously, it would seem. In a traditional Spring application, I typically add this to the XML configuration:<task:annotation-driven /> <task:executor id="workExecutor" pool-size="1" queue-capacity="0" rejection-policy="DISCARD" />
So I thought writing the equivalent of this in the main application class would help:
@SpringBootApplication @EnableAsync public class Application { @Value("${pool.size:1}") private int poolSize;; @Value("${queue.capacity:0}") private int queueCapacity; @Bean(name="workExecutor") public TaskExecutor taskExecutor() { ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); taskExecutor.setMaxPoolSize(poolSize); taskExecutor.setQueueCapacity(queueCapacity); taskExecutor.afterPropertiesSet(); return taskExecutor; } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
This did not change the behavior. The Ajax response still arrives after 10 seconds of sending the request. What am I missing?
The Spring Boot application can be downloaded here. With Maven installed, the project can be run with the simple command:
mvn clean spring-boot:run
Note The issue was resolved thanks to the answer provided by @Dave Syer below, who pointed out that I was missing
@EnableAsync
in my application, even though I had the line in the code snippet above.-
xerx593 over 5 yearsPossible duplicate of @Async not working for me
-
-
Web User over 9 yearsI moved the
@Async
annotation from the controller methoddoSomeWork()
service method, as the service implementation is a@Service
bean. That did not change the behavior. -
Dave Syer over 9 yearsYou mean you added
@Async
to theDoSomeWorkServiceImpl.doSomeWork()
method? -
Web User over 9 yearsYes, moved the annotation from the controller method to the service method.
-
Dave Syer over 9 yearsMaybe you need
@EnableAsync(proxyTargetClass=true)
because your service doesn't have an interface. -
Dave Syer over 9 yearsThe "workExecutor" you set up is doing nothing by the way. You need to qualify the
@Async
for it to use a specific bean (or else add anAsyncConfigurer
). -
Web User over 9 yearsI have attached the project ZIP file. It is a really small, dummy application focusing on the problem at hand. I did end up qualifying the
@Async
as you suggested, after setting up the executor inApplication
.DoSomeWorkServiceImpl
is a@Service
bean and an implementation class, by the way. -
Dave Syer over 9 yearsIn your sample you don't
@EnableAsync
anywhere (although it was in the original question). If you add it the app works for me. -
Web User over 9 yearsSomewhere between pasting code snippets and packaging the application, I must have missed adding that in my environment. Sorry for the back-and-forth and thanks for your timely help!
-
ASten almost 6 yearsIs creation of the TaskExecutor necessary?
-
Aj Tech Developer almost 6 years@ASten, Yes, it is necessary.
-
Lord Nick almost 6 years@AjTechDeveloper are you sure it is necessary to have taskexecutor because i read somewhere, "By default, Spring uses a SimpleAsyncTaskExecutor to actually run these methods asynchronously." which can be overridden.
-
Vivek Sethi almost 6 yearsThe important thing to keep in mind is that, if the async bean is implementing an interface, by default the proxy will be a Java Dynamic Proxy. This means that only external method calls that come in through the proxy will be intercepted – any self-invocation calls will not start the async process – even if the method is annotated with @Async.
-
Aj Tech Developer over 5 years@Nirmit Srivastava, I have also read about this, but I have not got a chance to try it out.
-
Krzysztof Czelusniak over 5 years@AjTechDeveloper, so it's not necessary to create
TaskExecutor
. -
Victor Grazi over 5 yearsThis seems like a reasonable solution. Is there any reason it does not have any upticks?
-
Az.MaYo over 4 yearsMy service class doesn't have interface but it's @Async working fine. Could you please explain little bit if service doesn't have interfaces than we need @EnableAsync(proxyTargetClass=true).
-
Dave Syer over 4 yearsRecent versions of Spring try to guess the correct value for the
proxyTargetClass
flag. I guess you benefited from that. -
Mark Brown over 4 yearsI have confirmed @Nirmit's statement that you don't need to create the TaskExecutor. I followed the instructions here: spring.io/guides/gs/async-method I think it is important that your method has a return type of CompletableFuture<T>.
-
Tharsan Sivakumar about 4 yearsA very straight forward steps. Worked for me like a charm
-
a_a over 3 years@DaveSyer I have exactly the same situation here and it works fine (works asynchronously). But when I put a debug point in that async method response doesn't come immediately and only comes after I resume from debugging. What could be the reason?
-
namila007 almost 3 years@a_a I'm having the same issue, why is that?
-
theprogrammer over 2 yearsBut lets say the service has to return something? how would you handle that in an async way? if you do future.get it will be blocking right? Any suggestion?