When you’re building responsive applications, especially REST APIs, blocking operations can slow down performance. Imagine sending emails, logging audit trails, or fetching third-party data — you don’t want your main thread to wait.
That’s where Spring Boot @Async
programming comes in!
Using the @Async
annotation, you can run methods asynchronously in a background thread, freeing up your main process and improving responsiveness.
In this guide, we’ll cover:
- How to enable and use
@Async
- Return types like
void
,Future
, andCompletableFuture
- Exception handling
- Real-world scenarios

✅ Step 1: Enable Async Support
To use @Async
, annotate your configuration class or main class with @EnableAsync
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package com.kscodes.springboot; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableAsync; @SpringBootApplication @EnableAsync public class AsyncApp { public static void main(String[] args) { SpringApplication.run(AsyncApp.class, args); } } |
✏️ Step 2: Add a Method with @Async
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
package com.kscodes.springboot.service; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @Service public class EmailService { @Async public void sendEmail(String to, String content) { try { Thread.sleep(3000); // simulate delay System.out.println("📧 Email sent to " + to); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } |
🚀 Step 3: Call the Async Method
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
package com.kscodes.springboot.controller; import com.kscodes.springboot.service.EmailService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/notify") public class NotificationController { @Autowired private EmailService emailService; @PostMapping("/send") public String sendNotification(@RequestParam String email) { emailService.sendEmail(email, "Welcome!"); return "🟢 Request received. Email is being sent in the background."; } } |
Even though the email takes 3 seconds to send, the response is instant because it’s handled in a separate thread.
⏳ Async Return Types
✅ void
Best for fire-and-forget operations.
🧵 Future<T>
and CompletableFuture<T>
Let you track the result later.
1 2 3 4 5 6 7 |
@Async public CompletableFuture<String> fetchData() { return CompletableFuture.completedFuture("Data Ready!"); } |
Use .get()
or .thenApply()
to retrieve the value:
1 2 3 4 5 |
CompletableFuture<String> result = service.fetchData(); System.out.println("Response: " + result.get()); |
⚠️ Exception Handling in @Async Methods
You must handle exceptions manually for void
methods. For CompletableFuture
, you can use .exceptionally()
:
1 2 3 4 5 6 7 |
@Async public CompletableFuture<String> riskyTask() { throw new RuntimeException("Boom!"); } |
1 2 3 4 5 6 7 8 |
riskyTask() .exceptionally(ex -> { System.err.println("❌ Exception: " + ex.getMessage()); return "Fallback"; }); |
⚙️ Step 4: Custom Executor (Optional)
You can define your own thread pool:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
@Configuration public class AsyncConfig { @Bean(name = "taskExecutor") public Executor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(2); executor.setMaxPoolSize(4); executor.setQueueCapacity(500); executor.setThreadNamePrefix("Async-Thread-"); executor.initialize(); return executor; } } |
Then use:
1 2 3 4 5 6 7 |
@Async("taskExecutor") public void processHeavyTask() { // heavy logic } |
🧠 Real-World Use Cases
Use Case | Description |
---|---|
Sending Emails | Background email dispatch after registration |
Logging | Save logs/audits without blocking flow |
API Calls | Call external APIs concurrently |
File Processing | Parse large files without freezing UI |
Notification Systems | Push messages without delay |
✅ Summary
Async Programming in Spring Boot is a powerful technique to boost application responsiveness and handle non-blocking operations. Whether it’s email notifications or third-party integrations, offloading them to background threads keeps your app fast and scalable.
In this tutorial, you learned:
- How to enable and use
@Async
- Difference between
void
,Future
,CompletableFuture
- Exception handling for async tasks
- How to use a custom executor
Use Async Programming in Spring Boot wisely to improve performance, especially for microservices and REST APIs.