Spring Boot: Creating Asynchronous Methods Using @Async Annotation
Need help creating asynchronous methods using the @Async annotation? Check out this tutorial to learn how in Spring Boot.
Join the DZone community and get the full member experience.
Join For FreeIn this article, we’ll explore the asynchronous execution support in Spring or Spring Boot using Spring's @Async
annotation.
We will annotate a bean method; @Async
will make it execute in a separate thread, i.e. the caller will not wait for the completion of the called method.
If you have been already working on a Spring or Spring Boot application, and you have a requirement to use as an asynchronous mechanism, then these three quick steps will help you get started.
Step 1: Enable Async Support
Let’s start by enabling asynchronous processing with Java configuration by simply adding the @EnableAsync
to a configuration class: The @EnableAsync
annotation switches Spring’s ability to run @Async
methods in a background thread pool.
Step 2: Add @Async Annotation to a Method
Make sure that the method we are annotating with @Async
needs to be public so that it can be proxied. And, self-invocation doesn’t work because it bypasses the proxy and calls the underlying method directly.
Step 3: Executor (Customize of Default)
Let's customize the ThreadPoolTaskExecutor
. In our case, we want to limit the number of concurrent threads to two and limit the size of the queue to 500. There are many more things you can tune. By default, a SimpleAsyncTaskExecutor
is used.
@Bean("threadPoolTaskExecutor")
public TaskExecutor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(20);
executor.setMaxPoolSize(1000);
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setThreadNamePrefix("Async-");
return executor;
}
That's all, these are three quick steps that help you create asynchronous services using Spring or Spring Boot. Let's develop a complete example to demonstrate how we can create asynchronous services using Spring or Spring Boot.
Learn and master in Spring Boot at Spring Boot Tutorial
What We’ll Build
We’ll build a lookup service that queries GitHub user information and retrieves data through GitHub’s API. One approach to scaling services is to run expensive jobs in the background and wait for the results using Java’s CompletableFuture
interface. Java’s CompletableFuture
is an evolution of the regular Future
. It makes it easy to pipeline multiple asynchronous operations, merging them into a single asynchronous computation.
Tools and Technologies Used
- Spring Boot - 2.0.6.RELEASE
- JDK - 1.8 or later
- Spring Framework - 5.0.9 RELEASE
- Maven - 3.2+
- IDE - Eclipse or Spring Tool Suite (STS)
Create and Import Spring Boot Project
There are many ways to create a Spring Boot application. The simplest way is to use Spring Initializr at http://start.spring.io/, which is an online Spring Boot application generator. Look at the above diagram, we have specified the following details:
- Generate: Maven Project
- Java Version: 1.8 (Default)
- Spring Boot:2.0.4
- Group: net.javaguides.springboot
- Artifact: springboot-async-example
- Name: springboot-async-example
- Description: Demo project for Spring Boot
- Package Name : net.guides.springboot.springbootasyncexample
- Packaging: jar (This is the default value)
- Dependencies: Web
Once all the details are entered, click on the Generate Project button. It will generate a Spring Boot project and download it. Next, unzip the downloaded zip file and import it into your favorite IDE.
Project Directory Structure
Below, the diagram shows a project structure for reference:
The pom.xml File
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.guides.springboot</groupId>
<artifactId>springboot-async-example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springboot-async-example</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
<relativePath/>
<!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Create a Representation of a GitHub User
Let's create a GitHub User model class with name and blog fields.
package net.guides.springboot.springbootasyncexample.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class User {
private String name;
private String blog;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getBlog() {
return blog;
}
public void setBlog(String blog) {
this.blog = blog;
}
@Override
public String toString() {
return "User [name=" + name + ", blog=" + blog + "]";
}
}
Note that Spring uses the Jackson JSON library to convert GitHub’s JSON response into a User object. The @JsonIgnoreProperties
annotation signals Spring to ignore any attributes not listed in the class. This makes it easy to make REST calls and produce domain objects. In this article, we are only grabbing the name and the blog URL for demonstration purposes.
Create a GitHub Lookup Service
Next, we need to create a service that queries GitHub to find user information.
package net.guides.springboot.springbootasyncexample.service;
import java.util.concurrent.CompletableFuture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import net.guides.springboot.springbootasyncexample.model.User;
@Service
public class GitHubLookupService {
private static final Logger logger = LoggerFactory.getLogger(GitHubLookupService.class);
private final RestTemplate restTemplate;
public GitHubLookupService(RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder.build();
}
@Async("threadPoolTaskExecutor")
public CompletableFuture < User > findUser(String user) throws InterruptedException {
logger.info("Looking up " + user);
String url = String.format("https://api.github.com/users/%s", user);
User results = restTemplate.getForObject(url, User.class);
// Artificial delay of 1s for demonstration purposes
Thread.sleep(1000 L);
return CompletableFuture.completedFuture(results);
}
}
The GitHubLookupService
class uses Spring’s RestTemplate
to invoke a remote REST point (api.github.com/users/) and then convert the answer into a User object. Spring Boot automatically provides a RestTemplateBuilder
that customizes the defaults with any auto-configuration bits (i.e. MessageConverter
). The findUser
method is flagged with Spring’s @Async
annotation, indicating it will run on a separate thread. The method’s return type is CompletableFuture
, instead of User
, a requirement for any asynchronous service. This code uses the completedFuture
method to return a CompletableFuture
instance, which is already complete with a result of the GitHub query.
Make the Application Executable
To run a sample, you can create an executable jar. Let's use CommandLineRunner
that injects the GitHubLookupService
and calls that service four times to demonstrate the method is executed asynchronously.
package net.guides.springboot.springbootasyncexample;
import java.util.concurrent.CompletableFuture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import net.guides.springboot.springbootasyncexample.model.User;
import net.guides.springboot.springbootasyncexample.service.GitHubLookupService;
@SpringBootApplication
@EnableAsync
public class SpringbootAsyncApplication implements CommandLineRunner {
private static final Logger logger = LoggerFactory.getLogger(SpringbootAsyncApplication.class);
@Autowired
private GitHubLookupService gitHubLookupService;
@Bean("threadPoolTaskExecutor")
public TaskExecutor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(20);
executor.setMaxPoolSize(1000);
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setThreadNamePrefix("Async-");
return executor;
}
public static void main(String[] args) {
SpringApplication.run(SpringbootAsyncApplication.class, args);
}
@Override
public void run(String...args) throws Exception {
// Start the clock
long start = System.currentTimeMillis();
// Kick of multiple, asynchronous lookups
CompletableFuture < User > page1 = gitHubLookupService.findUser("PivotalSoftware");
CompletableFuture < User > page2 = gitHubLookupService.findUser("CloudFoundry");
CompletableFuture < User > page3 = gitHubLookupService.findUser("Spring-Projects");
CompletableFuture < User > page4 = gitHubLookupService.findUser("RameshMF");
// Wait until they are all done
CompletableFuture.allOf(page1, page2, page3, page4).join();
// Print results, including elapsed time
logger.info("Elapsed time: " + (System.currentTimeMillis() - start));
logger.info("--> " + page1.get());
logger.info("--> " + page2.get());
logger.info("--> " + page3.get());
logger.info("--> " + page4.get());
}
}
The @EnableAsync
annotation switches on Spring’s ability to run @Async
methods in a background thread pool. This class also customizes the used Executor
. In our case, we want to limit the number of concurrent threads to two and limit the size of the queue to 500. There are many more things you can tune. By default, a SimpleAsyncTaskExecutor
is used.
Running Application
There are two ways we can start the standalone Spring Boot application.
- We are using Maven to run the application using ./mvnw spring-boot:run. Or, you can build the JAR file with ./mvnw clean package. Then, you can run the JAR file:
java -jar target/springboot-async-example.jar
- Below diagram shows how to run your Spring Boot application from an IDE — right click, run the
SpringbootAsyncApplication.main()
method as a standalone Java class.
Output
When we run the application, we will see the following output:
Learn and master in Spring Boot at Spring Boot Tutorial
The source code of this article available on my GitHub repository.
References
Published at DZone with permission of Ramesh Fadatare. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments