Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Issue with a Spring @Async method not catching/rethrowing an exception

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...

like image 402
balteo Avatar asked Jun 20 '14 16:06

balteo


People also ask

What will happen if you specify @async over a public method of a Spring bean?

Simply put, annotating a method of a bean with @Async will make it execute in a separate thread. In other words, the caller will not wait for the completion of the called method. One interesting aspect in Spring is that the event support in the framework also has support for async processing if necessary.

How do you enable @async annotation on query method?

To enable the asynchronous processing, add the @EnableAsync annotation to the configuration class. The @EnableAsync annotation switches on Spring's ability to run @Async methods in a background thread pool.

Does @async work on private methods?

Never use @Async on top of a private method. In runtime, it will not able to create a proxy and, therefore, not work. 3. Never write an Async method in the same class where the caller method invokes the same Async methodAsync method in the same class where the caller method invokes the same Async method.

What happens if an exception is thrown within an asynchronous method?

Async void methods have different error-handling semantics. When an exception is thrown out of an async Task or async Task method, that exception is captured and placed on the Task object.


2 Answers

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.

like image 183
Sotirios Delimanolis Avatar answered Oct 04 '22 01:10

Sotirios Delimanolis


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();
      }
    }
    
like image 34
Giampiero Poggi Avatar answered Oct 01 '22 01:10

Giampiero Poggi