Spring Boot Asynchronous Methods using @Async Annotation

In this article, we’ll explore the asynchronous execution support in Spring or Spring Boot using Spring's @Async annotation.
We will annotate a method of a bean with @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 Spring or Spring Boot Application and you have a requirement to use an Asynchronous mechanism, then these below three quick steps will help to set up.

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 on 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 2 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 help you to create asynchronous services using Spring or Spring Boot.
Let's develop a complete example to demonstrates how to create asynchronous services using Spring or Spring Boot.

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 - 3
  • JDK - 17 or later
  • Spring Framework - 6
  • Maven - 3.2+
  • IDE - Eclipse or Spring Tool Suite (STS)

Create and Set up the 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.
Use the following details while generating a Spring Boot project using Spring Initializr:
  • Generate: Maven Project
  • Java Version: 17 (Default)
  • Spring Boot: 3.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, then click on Generate Project button will generate a spring boot project and downloads 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>3.0.4</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>17</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 completed 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 which injects the GitHubLookupService and calls that service 4 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 2 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

Next, run the Spring Boot application. Refer to the below screenshot:

Output

When we run the application, we will see the following output:

Reference

GitHub

The source code of this tutorial is available on my GitHub Repository.

Comments

Post a Comment

Leave Comment