Reputation: 2030
I've got the following controller advice:
@ControllerAdvice
public class ExceptionHandlerAdvice {
@ExceptionHandler(NotCachedException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ModelAndView handleNotCachedException(NotCachedException ex) {
LOGGER.warn("NotCachedException: ", ex);
return generateModelViewError(ex.getMessage());
}
}
It works great most of the time but when the NotCachedException is thrown from a method annotated with @Async, the exception is not handled properly.
@RequestMapping(path = "", method = RequestMethod.PUT)
@Async
public ResponseEntity<String> store(@Valid @RequestBody FeedbackRequest request, String clientSource) {
cachingService.storeFeedback(request, ClientSource.from(clientSource));
return new ResponseEntity<>(OK);
}
Here is the config of the Executor:
@SpringBootApplication
@EnableAsync
public class Application {
private static final Logger LOGGER = LoggerFactory.getLogger(Application.class);
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
SettingsConfig settings = context.getBean(SettingsConfig.class);
LOGGER.info("{} ({}) started", settings.getArtifact(), settings.getVersion());
createCachingIndex(cachingService);
}
@Bean(name = "matchingStoreExecutor")
public Executor getAsyncExecutor() {
int nbThreadPool = 5;
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(nbThreadPool);
executor.setMaxPoolSize(nbThreadPool * 2);
executor.setQueueCapacity(nbThreadPool * 10);
executor.setThreadNamePrefix("matching-store-executor-");
executor.initialize();
return executor;
}
}
What can I do in order to make it work with @Async annotated methods?
Upvotes: 9
Views: 5420
Reputation: 8798
@Async shouldn't be used on controller methods as you would have one thread per request anyway. More details: is using @Async and CompletableFuture in controller can increase performance of our api?
If a service method like cachingService.storeFeedback
takes long time to be completed and you want immediate response from endpoint then it could be annotated @Async
in the service layer so cachingService.storeFeedback
could be executed in the background.
For controller method below, neither @ExceptionHandler
nor previous answer would help to return HTTP error response if exception would occur in the void @Async
service method:
@RequestMapping(path = "", method = RequestMethod.PUT)
public ResponseEntity<String> store(@Valid @RequestBody FeedbackRequest request, String clientSource) {
cachingService.storeFeedback(request, ClientSource.from(clientSource));
return new ResponseEntity<>(OK);
}
If controller would return Future
from @Async
service then @ExceptionHandler
should be sufficient to handle endpoint errors:
@RequestMapping(path = "", method = RequestMethod.PUT)
public CompletableFuture<String> store(@Valid @RequestBody FeedbackRequest request, String clientSource) {
return cachingService.storeFeedback(request, ClientSource.from(clientSource));
}
According to How To Do @Async in Spring:
When a method return type is a Future, exception handling is easy. Future.get() method will throw the exception.
But if the return type is void, exceptions will not be propagated to the calling thread. So, we need to add extra configurations to handle exceptions.
Upvotes: 0
Reputation: 1475
The default exception handling machenism does not work in case of @Async Enabled. To handle exception thrown from methods annotated with @Async, you need to implement a custom AsyncExceptionHandler as.
public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler{
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
// Here goes your exception handling logic.
}
}
Now You need to configure this customExceptionHandler in you Application class as
@EnableAsync
public class Application implements AsyncConfigurer {
@Override Executor getAsyncExecutor(){
// your ThreadPoolTaskExecutor configuration goes here.
}
@Override
public AsyncUncaughExceptionHandler getAsyncUncaughtExceptionHandler(){
return new AsyncExceptionHandler();
}
Note: Make sure in order to make your AsyncExceptionHandler work you need to implement AsyncConfigurer in your Application class.
Upvotes: 8