BadPractice8 - SpotBugsExtensionForSpringFrameWork/CS5098 GitHub Wiki

Bad practice - Proper ways to handle exceptions of @Async method with void return type

Description

To handle exceptions that emerged inside a thread, developers could have have some problems that described in the correctness error. For example, some of them may try to use try-catch block to catch exceptions that made by void return type. At the same time, some may wnat to use @ExceptionHandler to deal with it. Obviously, those are correctness problems.

Therefore, I would like to introduce what should we do handle exceptions that cause by asynchonous method in Spring.


Theory

With @EnableAsync annotation, Spring will enable its asynchonous method execution capability. Without any configuration, Spring will search for a org.springframework.core.task.TaskExecutor bean or an java.util.concurrent.Executor bean as the thread pool definition.(@EnableAsync Doc)

  • void return type

However, in this default case, exceptions thrown by void return type are uncaught exceptions. ErrorHandler in base class AbstractMessageListenerContainer will be invoked while there is an uncaught exception, but the ErrorHanler will only perform error-level logging.

	/**
	 * Set the ErrorHandler to be invoked in case of any uncaught exceptions thrown
	 * while processing a Message.
	 * <p>By default, there will be <b>no</b> ErrorHandler so that error-level
	 * logging is the only result.
	 */
	public void setErrorHandler(@Nullable ErrorHandler errorHandler) {
		this.errorHandler = errorHandler;
	}

So, these uncaught exceptions will not be propagated to the caller and can not be handled correctly.

In this scenario, Spring provides developers some ways to customize the their own AsyncUncaughtExceptionHandler, including to implement interface AsyncConfigurer, to extends class AsyncConfigurerSupport which implements AsyncConfigurer.


Solution

There are two solutions for asynchonous method exception handling, each of those is for certain return type.

  • void return type

For those have void return type, we have two choices. We can implement AsyncConfigurer interface or extends AsyncConfigurerSupport class to customize AsyncUncaughtExceptionHandler.

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new AsyncUncaughtExceptionHandler() {

            @Override
            public void handleUncaughtException(Throwable ex, Method method, Object... params) {
                ErrorLogger.getInstance().log(String.format("Invoking async method: '%s'", method), ex);
            }

        };
    }
}

Or we can simply extends AsyncConfigurerSupport for only AsyncUncaughtExceptionHandler.

@Configuration
@EnableAsync
public class AsyncConfig extends AsyncConfigurerSupport {

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new AsyncUncaughtExceptionHandler() {

            @Override
            public void handleUncaughtException(Throwable ex, Method method, Object... params) {
                
                ErrorLogger.getInstance().log(String.format("Invoking async method: '%s'", method), ex);
            }

        };
    }
}
⚠️ **GitHub.com Fallback** ⚠️