Spring @Async Annotation for Asynchronous Processing

Filed Under: Spring

Spring @Async annotation allows us to create asynchronous methods in spring. Let’s explore @Async in this tutorial on spring framework.

For a brief, when we annotate a method of a bean @Async annotation, Spring will execute it in a separate thread and the caller of the method will not wait till the method is completed execution. We will be defining our own Service and using Spring Boot 2 in this example. Let’s get started!

Spring @Async Example

We will be using Maven to create a sample project for the demonstration. To create the project, execute the following command in a directory that you will use as workspace:


mvn archetype:generate -DgroupId=com.journaldev.asynchmethods -DartifactId=JD-SpringBoot-AsyncMethods -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

If you are running maven for the first time, it will take a few seconds to accomplish the generate command because maven has to download all the required plugins and artifacts in order to make the generation task. Here is how project creation looks like:

spring @async example maven project

Creating a project with Maven

Once you have created the project, feel free to open it in your favourite IDE. Next step is to add appropriate Maven Dependencies to the project. Here is the pom.xml file with the appropriate dependencies:


<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.1.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<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>

Finally, to understand all the JARs which are added to the project when we added this dependency, we can run a simple Maven command which allows us to see a complete Dependency Tree for a project when we add some dependencies to it. Here is a command which we can use:


mvn dependency:tree

When we run this command, it will show us the following Dependency Tree:
spring async maven dependencies

Enabling Async Support

Enabling Async support is as well, just a matter of a single annotation. Apart from enabling the Async execution, we will also make use of Executor which allow us to define Thread limits as well. More on this once we write the code:


package com.journaldev.asynchexample;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@SpringBootApplication
@EnableAsync
public class AsyncApp {
    ...
}

Here we made use of @EnableAsync annotation which enables Spring’s ability to run Asynchronous methods in a background thread pool.

Next, we also add the mentioned Executor:


@Bean
public Executor asyncExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(2);
    executor.setMaxPoolSize(2);
    executor.setQueueCapacity(500);
    executor.setThreadNamePrefix("JDAsync-");
    executor.initialize();
    return executor;
}

Here, we set that maximum of 2 threads should run concurrently and the queue size is set to 500. Here is the complete code of the class with import statements:


package com.journaldev.asynchexample;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@SpringBootApplication
@EnableAsync
public class AsyncApp {

    public static void main(String[] args) {
        SpringApplication.run(AsyncApp.class, args).close();
    }

    @Bean
    public Executor asyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(2);
        executor.setQueueCapacity(500);
        executor.setThreadNamePrefix("JDAsync-");
        executor.initialize();
        return executor;
    }
}

We will make a service next which actually makes of Thread executions.

Making a Model

We will be using a public Movie API which just returns a Movie’s Data. We will be defining our model for the same:


package com.journaldev.asynchexample;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public class MovieModel {

    private String title;
    private String producer;

    // standard getters and setters

    @Override
    public String toString() {
        return String.format("MovieModel{title='%s', producer='%s'}", title, producer);
    }
}

We have used @JsonIgnoreProperties so that if there are more attributes in the response, they can be safely ignored by Spring.

Making the Service

It’s time we define our Service which will be calling the mentioned Movie API. We will use a simple RestTemplate to hit a GET API and obtain results asynchronously. Let’s look at the sample code we use:


package com.journaldev.asynchexample;

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 java.util.concurrent.CompletableFuture;

@Service
public class MovieService {

    private static final Logger LOG = LoggerFactory.getLogger(MovieService.class);

    private final RestTemplate restTemplate;

    public MovieService(RestTemplateBuilder restTemplateBuilder) {
        this.restTemplate = restTemplateBuilder.build();
    }

    @Async
    public CompletableFuture lookForMovie(String movieId) throws InterruptedException {
        LOG.info("Looking up Movie ID: {}", movieId);
        String url = String.format("https://ghibliapi.herokuapp.com/films/%s", movieId);
        MovieModel results = restTemplate.getForObject(url, MovieModel.class);
        // Artificial delay of 1s for demonstration purposes
        Thread.sleep(1000L);
        return CompletableFuture.completedFuture(results);
    }
}

This class is a @Service which makes it eligible for Spring Component Scan. The lookForMovie method’s return type is CompletableFuture which is a requirement for any asynchronous service. As timing for the API can vary, we have added a delay of 2 second for demonstration.

Making a Command Line Runner

We will be running our app using a CommandLineRunner which is the easiest way to test our application. A CommandLineRunner runs right after all the beans of the application has been initialised. Let’s see the code for CommandLineRunner:


package com.journaldev.asynchexample;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import java.util.concurrent.CompletableFuture;

@Component
public class ApplicationRunner implements CommandLineRunner {

    private static final Logger LOG = LoggerFactory.getLogger(ApplicationRunner.class);

    private final MovieService movieService;

    public ApplicationRunner(MovieService movieService) {
        this.movieService = movieService;
    }


    @Override
    public void run(String... args) throws Exception {
        // Start the clock
        long start = System.currentTimeMillis();

        // Kick of multiple, asynchronous lookups
        CompletableFuture<MovieModel> page1 = movieService.lookForMovie("58611129-2dbc-4a81-a72f-77ddfc1b1b49");
        CompletableFuture<MovieModel> page2 = movieService.lookForMovie("2baf70d1-42bb-4437-b551-e5fed5a87abe");
        CompletableFuture<MovieModel> page3 = movieService.lookForMovie("4e236f34-b981-41c3-8c65-f8c9000b94e7");

        // Join all threads so that we can wait until all are done
        CompletableFuture.allOf(page1, page2, page3).join();

        // Print results, including elapsed time
        LOG.info("Elapsed time: " + (System.currentTimeMillis() - start));
        LOG.info("--> " + page1.get());
        LOG.info("--> " + page2.get());
        LOG.info("--> " + page3.get());
    }
}

We just used the RestTemaplate to hit the sample API we used with some randomly picked Movie IDs. We will be running our application to see what output it shows.

Running the application

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


2018-04-13  INFO 17868 --- [JDAsync-1] c.j.a.MovieService  : Looking up Movie ID: 58611129-2dbc-4a81-a72f-77ddfc1b1b49
2018-04-13 08:00:09.518  INFO 17868 --- [JDAsync-2] c.j.a.MovieService  : Looking up Movie ID: 2baf70d1-42bb-4437-b551-e5fed5a87abe
2018-04-13 08:00:12.254  INFO 17868 --- [JDAsync-1] c.j.a.MovieService  : Looking up Movie ID: 4e236f34-b981-41c3-8c65-f8c9000b94e7
2018-04-13 08:00:13.565  INFO 17868 --- [main] c.j.a.ApplicationRunner  : Elapsed time: 4056
2018-04-13 08:00:13.565  INFO 17868 --- [main] c.j.a.ApplicationRunner  : --> MovieModel{title='My Neighbor Totoro', producer='Hayao Miyazaki'}
2018-04-13 08:00:13.565  INFO 17868 --- [main] c.j.a.ApplicationRunner  : --> MovieModel{title='Castle in the Sky', producer='Isao Takahata'}
2018-04-13 08:00:13.566  INFO 17868 --- [main] c.j.a.ApplicationRunner  : --> MovieModel{title='Only Yesterday', producer='Toshio Suzuki'}

If you observe closely, only two threads were made to be executed in the app, namely JDAsync-1 and JDAsync-2.

Conclusion

In this lesson, we studied how we can use Spring’s Asynchronous capabilities with Spring Boot 2.

Read more Spring related posts here.

Download the Source Code

Leave a Reply

Your email address will not be published. Required fields are marked *

close
Generic selectors
Exact matches only
Search in title
Search in content
Search in posts
Search in pages