Spring Boot @Async Example (2024)
In most of the application, multithreading supports parallel processing. This allows us to make use
of the CPU's processing capabilities. We can handle our own threads by utilizing
@Async
annotation in
accordance with needs as each application by default has a different number of threads for a variety
of tasks.
Q: What is Spring Boot Async?
Ans:
A SimpleAsyncTaskExector
is used by Spring Boot to execute an async
method. By adding
the @Async
annotation to a bean method allows it to run in a different thread. In other
terms, the caller won't wait for the called method to finish.
Q: Is RestTemplate async?
Ans:
RestTemplate is synchronous and blocking because it makes use of the Java Servlet
API. To achieve the asynchronous, we can add @Async
annotation at the top of method
where we are using RestTemplate
or can utilize the webclient
for calling
any rest service endpoint in asynchronous way.
In contrast, as WebClient
is asynchronous, waiting for the response won't stop the
thread that is currently executing.
Refer Spring Boot WebClient Example for implementation.
Create Spring Boot application
Create Spring Boot application from Spring Initializr.
Project Structure
Add Dependencies
Add spring-boot-starter-web
dependency in pom.xml
.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.techgeeknext</groupId>
<artifactId>Spring-Boot-Async</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Spring-Boot-Async</name>
<description>Spring Boot Async Example</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Take a look at our suggested posts:
Spring Boot Async Configuration
@EnableAsync
enables Spring's ability to use@Async
methods, allowing asynchronous processing.- This Bean can be set up in the Spring Boot configuration class.
- A
SimpleAsyncTaskExector
is used by Spring Boot to execute an async method. - This Executor
asyncTaskExecutor
is active by default and can be changed either at the level of the specific methods or at the level of the application. - Depending on how many concurrent threads need to execute at once, we can modify our async executor configuration.
- Additionally, we can have several Async Executors with various thread configurations and specify
different bean names, similar to how we define
asyncTaskExecutor
.
package com.techgeeknext.config;
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 AsyncConfiguration {
@Bean(name = "asyncTaskExecutor")
public Executor asyncTaskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(4);
taskExecutor.setQueueCapacity(150);
taskExecutor.setMaxPoolSize(4);
taskExecutor.setThreadNamePrefix("AsyncTaskThread-");
taskExecutor.initialize();
return taskExecutor;
}
}
Spring Boot Async Annotation
- To complete a thread's task without interfering with another parallel process, add the
@Async
annotation to a service method. - A method with a return type can also use
@Async
by enclosing the actual return in aCompletableFuture
.
package com.techgeeknext.service;
import com.techgeeknext.model.Employee;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Service
@Slf4j
public class EmployeeService {
@Async("asyncTaskExecutor")
public CompletableFuture<List<Employee>> getAllEmployees() throws InterruptedException {
log.info("------getAllEmployees started-----");
//To obtain records, we can add logic that calls another service or database.
Employee employee1 = Employee.builder()
.name("TechGeekNextUser")
.role("Admin")
.id(1)
.build();
Employee employee2 = Employee.builder()
.name("User-2")
.role("Supervisor")
.id(2)
.build();
List<Employee> employees = new ArrayList<>();
employees.add(employee1);
employees.add(employee2);
//added delay for test async
Thread.sleep(6000L);
log.info("------getAllEmployees completed-----");
return CompletableFuture.completedFuture(employees);
}
@Async("asyncTaskExecutor")
public CompletableFuture<Employee> getEmployeeById() throws InterruptedException {
log.info("------getEmployeeById started-----");
//To obtain records, we can add logic that calls another service or database.
Thread.sleep(2000L);
log.info("------getEmployeeById completed-----");
return CompletableFuture.completedFuture(Employee.builder()
.name("TechGeekNextUser")
.role("Admin")
.id(1)
.build());
}
@Async("asyncTaskExecutor")
public CompletableFuture<String> getEmployeeRoleById() throws InterruptedException {
log.info("------getEmployeeRoleById started-----");
//To obtain records, we can add logic that calls another service or database.
Thread.sleep(4000L);
log.info("------getEmployeeRoleById completed-----");
return CompletableFuture.completedFuture("Admin");
}
}
Controller to test Spring Boot Async
Call the service methods to test the Async behaviour.
package com.techgeeknext.controller;
import com.techgeeknext.model.Employee;
import com.techgeeknext.service.EmployeeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@RestController
@Slf4j
public class EmployeeController {
@Autowired
EmployeeService employeeService;
/**
* Test the async example
*
* @return ResponseEntity
*/
@GetMapping("/async/test")
public ResponseEntity<String> getEmployees() {
try {
CompletableFuture<List<Employee>> employees = employeeService.getAllEmployees();
CompletableFuture<Employee> employee = employeeService.getEmployeeById();
CompletableFuture<String> employeeRole = employeeService.getEmployeeRoleById();
// Hold off until every above async job is finished.
CompletableFuture.allOf(employees, employee, employeeRole).join();
// instead of using join, we can also use isDone for that method to see if it's completed or not
//if (employees.isDone()) { employees.get()}
//if (employee.isDone()) { employee.get()}
//if (employeeRole.isDone()) { employeeRole.get()}
//Print all of the collected data.
log.info("All Employees--> " + employees.get());
log.info("Employee By Id--> " + employee.get());
log.info("Employee Role By Id--> " + employeeRole.get());
return new ResponseEntity<>("The async example worked perfectly!", HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
Test Spring Boot Async Example
- Start the Spring Boot Application by running
spring-boot:run
or by running main class. - Use GET method with end point http://localhost:8080/async/test which invoke the async methods.
- After calling the above rest endpoint, we can observe in the console logs that
async
methods are being executed concurrently and without waiting for other tasks to complete. Each task is independent from the others.. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.7.2) 12:54:10.736 INFO 22584 --- [main] c.t.SpringBootAsyncApplication: Starting SpringBootAsyncApplication using Java 1.8.0_91 on 22584 (D:\Spring-Boot-Async\target\classes started in D:\Spring-Boot-Async) 12:54:10.740 INFO 22584 --- [main] c.t.SpringBootAsyncApplication: No active profile set, falling back to 1 default profile: "default" 12:54:11.901 INFO 22584 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) 12:54:11.909 INFO 22584 --- [main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 12:54:11.909 INFO 22584 --- [main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.65] 12:54:12.012 INFO 22584 --- [main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 12:54:12.012 INFO 22584 --- [main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1220 ms 12:54:12.344 INFO 22584 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 12:54:12.350 INFO 22584 --- [main] c.t.SpringBootAsyncApplication: Started SpringBootAsyncApplication in 1.97 seconds (JVM running for 2.35) 12:54:30.609 INFO 22584 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' 12:54:30.609 INFO 22584 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' 12:54:30.610 INFO 22584 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms 12:54:30.652 INFO 22584 --- [yncTaskThread-2] c.techgeeknext.service.EmployeeService : ------getEmployeeById started----- 12:54:30.652 INFO 22584 --- [yncTaskThread-3] c.techgeeknext.service.EmployeeService : ------getEmployeeRoleById started----- 12:54:30.652 INFO 22584 --- [yncTaskThread-1] c.techgeeknext.service.EmployeeService : ------getAllEmployees started----- 12:54:32.653 INFO 22584 --- [yncTaskThread-2] c.techgeeknext.service.EmployeeService : ------getEmployeeById completed----- 12:54:34.661 INFO 22584 --- [yncTaskThread-3] c.techgeeknext.service.EmployeeService : ------getEmployeeRoleById completed----- 12:54:36.656 INFO 22584 --- [yncTaskThread-1] c.techgeeknext.service.EmployeeService : ------getAllEmployees completed----- 12:54:36.657 INFO 22584 --- [nio-8080-exec-1] c.t.controller.EmployeeController : All Employees--> [Employee(id=1, name=TechGeekNextUser, role=Admin), Employee(id=2, name=User-2, role=Supervisor)] 12:54:36.657 INFO 22584 --- [nio-8080-exec-1] c.t.controller.EmployeeController : Employee By Id--> Employee(id=1, name=TechGeekNextUser, role=Admin) 12:54:36.658 INFO 22584 --- [nio-8080-exec-1] c.t.controller.EmployeeController : Employee Role By Id--> Admin
@Async Annotation Limitation
- Only public methods are compatible with async annotation.
- It won't function if async methods are called from within the same class. This indicates that async processing prevents self invocation.
- If you use autorun tools like Dev Tool in your project, it behaves inconsistently.
Download Source Code
The full source code for this article can be found below.
- Download it here - Spring Boot Async Example