10

I am facing an issue since I enabled turned a synchronous method into an asynchronous one: if an exception is thrown from within the body of the method, I can no longer rethrow it.

Let me first show the code:

My async/task executor configuration:

@Configuration
@EnableAsync
public class AsyncConfiguration implements AsyncConfigurer {

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

My asynchronous method:

@Async
@Override
public void sendPasswordResetInfo(String email) {
    Assert.hasText(email);
    Member member = memberRepository.findByEmail(email);
    try {
        mailerService.doMailPasswordResetInfo(member);//EXCEPTION THROWN HERE
    } catch (MessagingException | MailSendException e) {
        log.error("MessagingException | MailSendException", e);
        // TODO: not thrown since @Async is used
        throw new MailerException("MessagingException | MailSendException");//NOT CALLED
    }
}

All I see in the console when an exception is raised by mailerService.doMailPasswordResetInfo is the following stacktrace:

2014-06-20 18:46:29,249 [ThreadPoolTaskExecutor-1] ERROR com.bignibou.service.preference.PreferenceServiceImpl - MessagingException | MailSendException
org.springframework.mail.MailSendException: Failed messages: javax.mail.SendFailedException: Invalid Addresses;
  nested exception is:
    com.sun.mail.smtp.SMTPAddressFailedException: 550 5.1.1 Adresse d au moins un destinataire invalide. Invalid recipient. OFR204_418 [418]
; message exception details (1) are:
Failed message 1:
javax.mail.SendFailedException: Invalid Addresses;
  nested exception is:
    com.sun.mail.smtp.SMTPAddressFailedException: 550 5.1.1 Adresse d au moins un destinataire invalide. Invalid recipient. OFR204_418 [418]

    at com.sun.mail.smtp.SMTPTransport.rcptTo(SMTPTransport.java:1294)
    at com.sun.mail.smtp.SMTPTransport.sendMessage(SMTPTransport.java:635)
    at org.springframework.mail.javamail.JavaMailSenderImpl.doSend(JavaMailSenderImpl.java:424)
    at org.springframework.mail.javamail.JavaMailSenderImpl.send(JavaMailSenderImpl.java:346)
    at org.springframework.mail.javamail.JavaMailSenderImpl.send(JavaMailSenderImpl.java:341)
    at com.bignibou.service.mailer.MailerServiceImpl.doMailPasswordResetInfo(MailerServiceImpl.java:82)
    at com.bignibou.service.preference.PreferenceServiceImpl.sendPasswordResetInfo_aroundBody10(PreferenceServiceImpl.java:112)
    at com.bignibou.service.preference.PreferenceServiceImpl$AjcClosure11.run(PreferenceServiceImpl.java:1)
    at org.springframework.transaction.aspectj.AbstractTransactionAspect.ajc$around$org_springframework_transaction_aspectj_AbstractTransactionAspect$1$2a73e96cproceed(AbstractTransactionAspect.aj:59)
    at org.springframework.transaction.aspectj.AbstractTransactionAspect$AbstractTransactionAspect$1.proceedWithInvocation(AbstractTransactionAspect.aj:65)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:262)
    at org.springframework.transaction.aspectj.AbstractTransactionAspect.ajc$around$org_springframework_transaction_aspectj_AbstractTransactionAspect$1$2a73e96c(AbstractTransactionAspect.aj:63)
    at com.bignibou.service.preference.PreferenceServiceImpl.sendPasswordResetInfo_aroundBody12(PreferenceServiceImpl.java:108)
    at com.bignibou.service.preference.PreferenceServiceImpl$AjcClosure13.run(PreferenceServiceImpl.java:1)
    at org.springframework.scheduling.aspectj.AbstractAsyncExecutionAspect.ajc$around$org_springframework_scheduling_aspectj_AbstractAsyncExecutionAspect$1$6c004c3eproceed(AbstractAsyncExecutionAspect.aj:58)
    at org.springframework.scheduling.aspectj.AbstractAsyncExecutionAspect.ajc$around$org_springframework_scheduling_aspectj_AbstractAsyncExecutionAspect$1$6c004c3e(AbstractAsyncExecutionAspect.aj:62)
    at com.bignibou.service.preference.PreferenceServiceImpl.sendPasswordResetInfo(PreferenceServiceImpl.java:108)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
    at org.springframework.aop.interceptor.AsyncExecutionInterceptor$1.call(AsyncExecutionInterceptor.java:97)
    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)
Caused by: com.sun.mail.smtp.SMTPAddressFailedException: 550 5.1.1 Adresse d au moins un destinataire invalide. Invalid recipient. OFR204_418 [418]

    at com.sun.mail.smtp.SMTPTransport.rcptTo(SMTPTransport.java:1145)
    ... 28 more

FYI, MailerException is a custom exception I used in the app. What really strikes me is that this MailerException exception does not appear to be caught nor rethrown...

Can anyone please help?

edit 1: I have modified my async service method as follows:

@Async
    @Override
    public Future<Void> sendPasswordResetInfo(String email) {
        Assert.hasText(email);
        Member member = memberRepository.findByEmail(email);
        try {
            mailerService.doMailPasswordResetInfo(member);
            return new AsyncResult<Void>(null);
        } catch (MessagingException | MailSendException e) {
            log.error("MessagingException | MailSendException", e);
            //HERE: HOW DO I SEND THE MailerException USING THE FUTURE??
            throw new MailerException("MessagingException | MailSendException");
        }
    }

However, I am not sure how to send the MailerException to the web layer using the AsyncResult from within the catch block above...

balteo
  • 23,602
  • 63
  • 219
  • 412
  • Check your imports... it is possible that you are using a different MessagingException. – Pavel Horal Jun 20 '14 at 16:58
  • 1
    Note that it doesn't make much sense to throw an exception and not return a `Future`. If you're running `@Async`, the exception will be caught by the `ExecutorService`, not by your code. – Sotirios Delimanolis Jun 20 '14 at 17:05
  • @SotiriosDelimanolis: very interesting. Can you please elaborate about returning a Future and exceptions.. – balteo Jun 20 '14 at 17:09
  • See the relevant documentation [here](http://docs.spring.io/spring/docs/current/spring-framework-reference/html/scheduling.html#scheduling-annotation-support-async). – Sotirios Delimanolis Jun 20 '14 at 17:11

3 Answers3

13

Your logs show

2014-06-20 18:46:29,249 [ThreadPoolTaskExecutor-1] ERROR com.bignibou.service.preference.PreferenceServiceImpl - MessagingException | MailSendException

which is logged by

log.error("MessagingException | MailSendException", e);

the Exception that is then thrown

throw new MailerException("MessagingException | MailSendException");//NOT CALLED

is caught by the Thread executed by the underlying ThreadPoolTaskExecutor. This code is run asynchronously so the exception is thrown in a different thread than the thread that invokes the method.

someCode();
yourProxy.sendPasswordResetInfo(someValue()); // exception thrown in other thread
moreCode();

If you make your method return a Future as shown here you'll be able to invoke get() on it and that will rethrow any exception that was thrown inside the @Async method, wrapped in an ExecutionException.

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
  • Thanks a lot. I now understand better what is going on. The solution you suggest is somewhat difficult to implement in my case as it would imply changing the return type of my service method (in order to return a `Future`) and changing the controller method (calling method) in order to invoke `get()`. Do you think a [AsyncUncaughtExceptionHandler](http://docs.spring.io/spring/docs/4.1.0.BUILD-SNAPSHOT/javadoc-api/org/springframework/aop/interceptor/AsyncUncaughtExceptionHandler.html) would help? (Spring 4.1) – balteo Jun 21 '14 at 05:04
  • @balteo Sure, depending on how you want to handle the exception. It seems that this `@Async` method is disconnected from the request after its been launched. – Sotirios Delimanolis Jun 21 '14 at 05:06
  • Oops. I forgot to mention I have an `@ExceptionHandler` in a `@ControllerAdvice` that is meant to handle the `MailerException` but that does not since I use @Async on the method. – balteo Jun 21 '14 at 05:16
  • By the way: do you have any idea how to configure and use a `AsyncUncaughtExceptionHandler` (`SimpleAsyncUncaughtExceptionHandler`)? – balteo Jun 21 '14 at 05:18
  • @balteo Spring MVC is a servlet based framework. Servlets (typically) work a on single threaded model. One thread handles the full request. So the same thread that invokes your controller's handler method would also invoke the `@ExceptionHandler` method. But you say you want the mailsender to run asynchronously but still have the `@ExceptionHandler` handle its exception. That doesn't make much sense. I hope you see what I mean. – Sotirios Delimanolis Jun 21 '14 at 05:20
  • @balteo That class is very new. I haven't tried the new Spring snapshot. – Sotirios Delimanolis Jun 21 '14 at 05:23
  • I understand what you mean about Servlets' single thread model. But how can the frontend somehow handle an exception thrown by an "asynchronous" thread? I need users to be notified that something went wrong... I am really stuck here and taking into account what you say,the AsyncUncaughtExceptionHandler is of no use to me... – balteo Jun 21 '14 at 12:16
  • @balteo Why does the send mail have to be async? The `Future` may be the only solution, so that the container thread blocks and waits for the async thread to finish. And then join its result. – Sotirios Delimanolis Jun 21 '14 at 14:29
  • Hi Sotirios! I guess I'll have to use a Future then. What is worrying me is that I have to modify all the controller methods that call async service methods returning Futures. Haven't I? – balteo Jun 21 '14 at 14:35
  • Also, how am I supposed to type parameterize the Future in this case? Future of Void? – balteo Jun 21 '14 at 14:38
  • @balteo Yes. Additionally, if you want handle exceptions with your `@ExceptionHandler`, you have to catch the `ExecutionException` from `get` and extract yours from the exception thrown and rethrow it. – Sotirios Delimanolis Jun 21 '14 at 14:39
  • I see, I have one interrogation still and I have edited my post accordingly. – balteo Jun 21 '14 at 14:53
  • 1
    @balteo Once you get the `Future` returned by `sendPasswordResetInfo`, call its `get()` somewhere down the line. That will throw an `ExecutionException` if an exception was thrown by the async method. That `ExecutionExeption` will have a `cause` which will be the exception actually thrown. You can retrieve it and rethrow it. – Sotirios Delimanolis Jun 21 '14 at 16:08
  • @balteo check out this link https://stackoverflow.com/questions/44138199/spring-exceptionhandler-and-multi-threading/45060491#45060491 it explains how to configure the AsyncUncaughtExceptionHandler. This configuration is required especially when the return type of your async method is void. – Vijender Kumar Jul 12 '17 at 18:52
4

By following this link http://www.baeldung.com/spring-async, you should create:

  1. CustomAsyncExceptionHandler that implements AsyncUncaughtExceptionHandler

    @Slf4j
    public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
      @Override
      public void handleUncaughtException(Throwable throwable, Method method, Object... obj) {
        log.error("**********************************************");
        log.error("Exception message - " + throwable.getMessage());
        log.error("Method name - " + method.getName());
        for (Object param : obj) {
            log.error("Parameter value - " + param);
        }
        log.error("**********************************************");
      }
    }
    
  2. SpringAsyncConfig that implements AsyncConfigurer

    @Configuration
    @EnableAsync
    public class SpringAsyncConfig implements AsyncConfigurer {
    
      @Override
      public Executor getAsyncExecutor() {
        return new ThreadPoolTaskExecutor();
      }
    
      @Override
      public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new CustomAsyncExceptionHandler();
      }
    }
    
Giampiero Poggi
  • 389
  • 1
  • 4
  • 13
  • This is all good an well, but how to then get exception somewherh outside the @Async method? Because in my case, I'd like to do some other dataabse operations based on the result – TheRealChx101 Jan 24 '23 at 11:37
0

The solution i had was fixed by creating a configuration class that extends AsyncConfigurerSupport, since the above solutions did not work in my implementation. In this configuration class, i had to define SimpleAsyncTaskExecutor and an async exception handler. Inside the handler, i checked the throwable instance and based on its value, i implemented the needed behavior:

if ( throwable instanceOf CustomException) {
// TODO add code
} else {
// TODO
}

Reference: Effective advice on spring async exception handler

user666
  • 1,750
  • 3
  • 18
  • 34