Detailed Guide to Using @EnableAsync and @Async for Asynchronous Bean Method Execution in Spring
This article provides a comprehensive explanation of Spring's @EnableAsync and @Async annotations, covering their purpose, usage steps, handling of return values, custom thread‑pool configuration, exception handling, thread‑pool isolation, and the underlying AOP mechanism, supplemented with complete code examples.
1. Overview
This section introduces the purpose of @EnableAsync and @Async: enabling asynchronous execution of bean methods in the Spring container.
2. Purpose
By annotating a bean method with @Async (and enabling it with @EnableAsync on a configuration class), the method will be invoked asynchronously, e.g., a logService.log(msg) call can run in a separate thread.
3. Basic Usage
Two Steps
Annotate the method (or the whole class) with @Async.
Add @EnableAsync to a Spring configuration class so that the @Async annotation takes effect.
Common Scenarios
Methods without a return value.
Methods that return a value.
4. Asynchronous Method Without Return Value
Usage
If the method does not return a Future, it returns immediately and its execution cannot be awaited.
@Async
public void log(String msg) throws InterruptedException {
System.out.println("Start logging," + System.currentTimeMillis());
// Simulate 2‑second delay
TimeUnit.SECONDS.sleep(2);
System.out.println("Log finished," + System.currentTimeMillis());
}Example
A LogService bean uses @Async to record logs asynchronously.
package com.javacode2018.async.demo1;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class LogService {
@Async
public void log(String msg) throws InterruptedException {
System.out.println(Thread.currentThread() + " start logging," + System.currentTimeMillis());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread() + " log finished," + System.currentTimeMillis());
}
}Configuration class to enable async:
package com.javacode2018.async.demo1;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableAsync;
@ComponentScan
@EnableAsync
public class MainConfig1 {}Test code demonstrates immediate return of the caller and later execution in a separate thread.
5. Obtaining Asynchronous Return Values
Usage
When a method returns a Future, the result can be retrieved via Future.get(). Use AsyncResult.forValue to create the return value.
public Future<String> getGoodsInfo(long goodsId) throws InterruptedException {
return AsyncResult.forValue(String.format("Goods %s basic info!", goodsId));
}Example Scenario
In an e‑commerce context, three independent methods fetch product info, description, and comments in parallel, reducing total latency from ~1.5 s to ~0.5 s.
package com.javacode2018.async.demo2;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
@Async
@Component
public class GoodsService {
public Future<String> getGoodsInfo(long goodsId) throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(500);
return AsyncResult.forValue(String.format("Goods %s basic info!", goodsId));
}
public Future<String> getGoodsDesc(long goodsId) throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(500);
return AsyncResult.forValue(String.format("Goods %s description!", goodsId));
}
public Future<List<String>> getGoodsComments(long goodsId) throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(500);
List<String> comments = Arrays.asList("Comment1", "Comment2");
return AsyncResult.forValue(comments);
}
}6. Custom Asynchronous Thread Pool
By default, Spring uses a built‑in thread pool. You can define your own by providing a bean named taskExecutor or by implementing AsyncConfigurer#getAsyncExecutor.
Method 1 – Define taskExecutor Bean
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(100);
executor.setThreadNamePrefix("my-thread-");
return executor;
}Method 2 – Implement AsyncConfigurer
@Configuration
@EnableAsync
public class MainConfig3 implements AsyncConfigurer {
@Bean
public Executor logExecutors() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(100);
executor.setThreadNamePrefix("log-thread-");
return executor;
}
@Override
public Executor getAsyncExecutor() {
return logExecutors();
}
}7. Custom Exception Handling
Two cases:
If the method returns Future, catch ExecutionException when calling Future.get().
If the method has no return value, provide an AsyncConfigurer that returns a custom AsyncUncaughtExceptionHandler.
Future‑Based Exception Example
try {
Future<String> future = logService.mockException();
System.out.println(future.get());
} catch (ExecutionException e) {
System.out.println("Caught ExecutionException");
e.getCause().printStackTrace();
}Void‑Method Exception Handler
@Bean
public AsyncConfigurer asyncConfigurer() {
return new AsyncConfigurer() {
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (ex, method, params) -> {
System.out.println(String.format("Method[%s], params[%s] threw an exception:", method, Arrays.asList(params)));
ex.printStackTrace();
};
}
};
}8. Thread‑Pool Isolation
Define separate thread‑pool beans and reference them via @Async("beanName") to prevent one business from monopolizing a shared pool.
Example Services
package com.javacode2018.async.demo5;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class RechargeService {
@Async(MainConfig5.RECHARGE_EXECUTORS_BEAN_NAME)
public void recharge() {
System.out.println(Thread.currentThread() + " async recharge");
}
} package com.javacode2018.async.demo5;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class CashOutService {
@Async(MainConfig5.CASHOUT_EXECUTORS_BEAN_NAME)
public void cashOut() {
System.out.println(Thread.currentThread() + " async cash out");
}
}Configuration defines two executors with distinct name prefixes ( recharge-thread- and cashOut-thread-).
9. Underlying Implementation
Spring registers AsyncAnnotationBeanPostProcessor, which creates AOP proxies for beans containing @Async. The proxy adds AsyncAnnotationAdvisor that delegates to AnnotationAsyncExecutionInterceptor, where the actual asynchronous invocation occurs.
10. Summary
The article demonstrates how to enable asynchronous method execution in Spring, how to retrieve results, customize thread pools, handle exceptions, isolate thread pools per business, and explains the AOP‑based mechanism behind @EnableAsync and @Async.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Full-Stack Internet Architecture
Introducing full-stack Internet architecture technologies centered on Java
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
