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...
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.
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.
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.
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.
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
.
By following this link http://www.baeldung.com/spring-async, you should create:
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("**********************************************"); } }
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(); } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With