Like @Transactional it is proxy-based, so the same self-invocation trap applies.
@EnableAsync
@Configuration
class AsyncConfig {
@Bean
Executor taskExecutor() {
var ex = new ThreadPoolTaskExecutor();
ex.setCorePoolSize(4);
ex.setQueueCapacity(100);
ex.setThreadNamePrefix("mail-");
ex.initialize();
return ex;
}
}
@Async
public CompletableFuture<Void> sendEmail(String to) { ... }
Caller returns immediately.
Thread name: mail-1 (not http-nio-8080-exec-1)
Run this yourself in the Online Java Compiler, spin up a live REST API in the API Sandbox, or practise with Java interview questions.
Published 2026-08-11