🚀 Introduction: What is @Async
in Spring Boot?
The @Async
annotation in Spring Boot allows asynchronous execution of methods in a separate thread, improving performance for long-running operations without blocking the main thread.
✅ Key Features of @Async
:
✔ Runs methods asynchronously without blocking execution.
✔ Uses Spring’s TaskExecutor to manage threads.
✔ Works with CompletableFuture<T>
, Future<T>
, and void
return types.
✔ Can be used for background tasks, I/O operations, and API calls.
📌 In this guide, you’ll learn:
✅ How to enable and use @Async
in Spring Boot.
✅ How to return values asynchronously.
✅ How to handle exceptions in async methods.
1️⃣ Enabling Async Execution in Spring Boot
Before using @Async
, enable asynchronous execution in your Spring Boot application.
📌 Enable @Async
in @Configuration
Class
@Configuration
@EnableAsync // Enables async execution
public class AsyncConfig {
}
✅ @EnableAsync
allows Spring to process @Async
methods asynchronously.
2️⃣ Basic Example: Running a Method Asynchronously
📌 Example: Running an Async Method in a Service
1. Create an Async Service (NotificationService.java
)
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class NotificationService {
@Async
public void sendEmail(String email) {
System.out.println("Sending email to: " + email);
try {
Thread.sleep(3000); // Simulating delay
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Email sent to: " + email);
}
}
2. Call Async Method from Controller (UserController.java
)
@RestController
@RequestMapping("/api/users")
public class UserController {
private final NotificationService notificationService;
public UserController(NotificationService notificationService) {
this.notificationService = notificationService;
}
@PostMapping("/register")
public String registerUser(@RequestParam String email) {
notificationService.sendEmail(email); // Async method call
return "User registered successfully. Email will be sent asynchronously.";
}
}
📌 POST Request (POST /api/users/register?email=test@example.com
)
📌 Console Output:
User registered successfully. Email will be sent asynchronously.
Sending email to: test@example.com
(After 3 seconds delay)
Email sent to: test@example.com
✅ The API responds immediately while email sending happens in the background.
3️⃣ Using CompletableFuture<T>
to Return Async Results
To return asynchronous results, use CompletableFuture<T>
.
📌 Example: Fetching Data Asynchronously
1. Async Service (DataService.java
)
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;
@Service
public class DataService {
@Async
public CompletableFuture<String> fetchData() {
try {
Thread.sleep(2000); // Simulate delay
} catch (InterruptedException e) {
e.printStackTrace();
}
return CompletableFuture.completedFuture("Fetched Data Successfully!");
}
}
2. Controller (DataController.java
)
@RestController
@RequestMapping("/api/data")
public class DataController {
private final DataService dataService;
public DataController(DataService dataService) {
this.dataService = dataService;
}
@GetMapping
public CompletableFuture<String> getData() {
return dataService.fetchData(); // Async execution
}
}
📌 GET Request (GET /api/data
)
📌 Response (after 2 seconds delay):
Fetched Data Successfully!
✅ The response is wrapped inside CompletableFuture<T>
and executed asynchronously.
4️⃣ Handling Exceptions in Async Methods
By default, exceptions in @Async
methods are lost if the return type is void
. To handle exceptions properly, return a CompletableFuture<T>
or log the error.
📌 Example: Handling Exceptions in Async Methods
1. Async Service (TaskService.java
)
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;
@Service
public class TaskService {
@Async
public CompletableFuture<String> processTask() {
try {
Thread.sleep(2000);
throw new RuntimeException("Something went wrong!");
} catch (Exception e) {
return CompletableFuture.failedFuture(e);
}
}
}
2. Controller (TaskController.java
)
@RestController
@RequestMapping("/api/tasks")
public class TaskController {
private final TaskService taskService;
public TaskController(TaskService taskService) {
this.taskService = taskService;
}
@GetMapping("/run")
public CompletableFuture<String> runTask() {
return taskService.processTask()
.exceptionally(ex -> "Error: " + ex.getMessage());
}
}
📌 GET Request (GET /api/tasks/run
)
📌 Response (HTTP 200 OK
):
Error: Something went wrong!
✅ Using exceptionally()
ensures that errors are handled properly.
5️⃣ Configuring Thread Pool for Async Execution
By default, Spring Boot uses a single-threaded executor for @Async
methods. You can define a custom thread pool for better performance.
📌 Example: Configuring a Thread Pool for @Async
Tasks
1. Define a Custom Thread Pool (AsyncConfig.java
)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "asyncExecutor")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("AsyncThread-");
executor.initialize();
return executor;
}
}
2. Use the Custom Executor in an Async Service
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;
@Service
public class ReportService {
@Async("asyncExecutor")
public CompletableFuture<String> generateReport() {
return CompletableFuture.completedFuture("Report Generated Successfully!");
}
}
✅ Now, Spring Boot will use the configured thread pool instead of the default one.
🎯 Summary: Best Practices for Using @Async
✅ Always enable async execution using @EnableAsync
.
✅ Use CompletableFuture<T>
instead of void
for better error handling.
✅ Configure a custom thread pool for scalability (ThreadPoolTaskExecutor
).
✅ Log exceptions in async methods (exceptionally()
for error handling).
✅ Use meaningful method names (fetchData()
, sendEmail()
, processTask()
).
🚀 Following these best practices ensures efficient, non-blocking execution in Spring Boot!
Comments
Post a Comment
Leave Comment