@Async chạy method trong thread pool riêng — non-blocking từ perspective của caller.
@SpringBootApplication
@EnableAsync
class App {}
@Service
class EmailService {
@Async
public CompletableFuture<Void> sendWelcome(String email) {
mailSender.send(email);
return CompletableFuture.completedFuture(null);
}
}
// Controller:
user = userService.save(user);
emailService.sendWelcome(user.getEmail()); // fire and forget
return ResponseEntity.ok(user); // return ngayThread pool config (quan trọng):
@Bean
TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor exec = new ThreadPoolTaskExecutor();
exec.setCorePoolSize(5);
exec.setMaxPoolSize(20);
exec.setQueueCapacity(100);
exec.initialize();
return exec;
}Không có custom executor: Spring dùng SimpleAsyncTaskExecutor (tạo thread mới mỗi task — không pool → OOM với tải cao).
Gotcha giống @Transactional: this.method() bypass proxy → không async. Gọi qua Spring bean khác.
@Async runs a method on a separate thread pool — non-blocking from the caller's perspective.
@SpringBootApplication
@EnableAsync
class App {}
@Service
class EmailService {
@Async
public CompletableFuture<Void> sendWelcome(String email) {
mailSender.send(email);
return CompletableFuture.completedFuture(null);
}
}
// Controller:
user = userService.save(user);
emailService.sendWelcome(user.getEmail()); // fire and forget
return ResponseEntity.ok(user); // returns immediatelyThread pool config (important):
@Bean
TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor exec = new ThreadPoolTaskExecutor();
exec.setCorePoolSize(5);
exec.setMaxPoolSize(20);
exec.setQueueCapacity(100);
exec.initialize();
return exec;
}Without a custom executor: Spring uses SimpleAsyncTaskExecutor (new thread per task — no pooling → OOM under high load).
Same gotcha as @Transactional: this.method() bypasses the proxy → not async. Always call through a Spring-managed bean.