Creating Asynchronous Methods


This guide walks you through the steps to create asynchronous queries to GitHub. The focus is on the asynchronous part, a feature often used when scaling services.


What you’ll build


You’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 from the regular Future. It makes it easy to pipeline multiple asynchronous operations merging them into a single asynchronous computation.


Create a representation of a GitHub User


Before you can create a GitHub lookup service, you need to define a representation for the data you’ll retrieve through GitHub’s API.


To model the user representation, you create a resource representation class. Provide a plain old Java object with fields, constructors, and accessors:


package hello;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

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;

    public String toString() {
        return "User [name=" + name + ", blog=" + blog + "]";


Spring使用Jackson JSON库将GitHub的JSON响应转换为User对象。@JsonIgnoreProperties注解提示Spring忽略不再此类中的属性。这将使REST调用更简单并生成域对象。

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 guide, we are only grabbing the name and the blog URL for demonstration purposes.


Create a GitHub lookup service


Next you need to create a service that queries GitHub to find user information.


package hello;

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;

public class GitHubLookupService {

    private static final Logger logger = LoggerFactory.getLogger(GitHubLookupService.class);

    private final RestTemplate restTemplate;

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

    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
        return CompletableFuture.completedFuture(results);


GitHubLookupService类使用了Spring的RestTemplate来调用一个远程REST端点(api.github.com/users/),并将响应转换为一个User对象。Spring Boot自动提供了一个RestTemplateBuilder来自定义一些自动配置(例如MessageConverter)的默认值。

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 class is marked with the @Service annotation, making it a candidate for Spring’s component scanning to detect it and add it to the application context.


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<User> 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 result of the GitHub query.


Creating a local instance of the GitHubLookupService class does NOT allow the findUser method to run asynchronously. It must be created inside a @Configuration class or picked up by @ComponentScan.


The timing for GitHub’s API can vary. To demonstrate the benefits later in this guide, an extra delay of one second has been added to this service.


Make the application executable


To run a sample, you can create an executable jar. Spring’s @Async annotation works with web apps, but you don’t need all the extra steps of setting up a web container to see its benefits.


package hello;

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;

public class Application {

    public static void main(String[] args) {
        // close the application context to shut down the custom ExecutorService
        SpringApplication.run(Application.class, args).close();

    public Executor asyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        return executor;



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.


There is also a CommandLineRunner that injects the GitHubLookupService and calls that service 3 times to demonstrate the method is executed asynchronously.


package hello;

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

import java.util.concurrent.CompletableFuture;

public class AppRunner implements CommandLineRunner {

    private static final Logger logger = LoggerFactory.getLogger(AppRunner.class);

    private final GitHubLookupService gitHubLookupService;

    public AppRunner(GitHubLookupService gitHubLookupService) {
        this.gitHubLookupService = gitHubLookupService;

    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");

        // Wait until they are all done

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




Build an executable JAR


Logging output is displayed, showing each query to GitHub. With the help of allOf factory method we create an array of CompletableFutures on which by calling the join method makes it possible to wait for the completion of all of the CompletableFutures.

2016-09-01 10:25:21.295  INFO 17893 --- [ GithubLookup-2] hello.GitHubLookupService                : Looking up CloudFoundry
2016-09-01 10:25:21.295  INFO 17893 --- [ GithubLookup-1] hello.GitHubLookupService                : Looking up PivotalSoftware
2016-09-01 10:25:23.142  INFO 17893 --- [ GithubLookup-1] hello.GitHubLookupService                : Looking up Spring-Projects
2016-09-01 10:25:24.281  INFO 17893 --- [           main] hello.AppRunner                          : Elapsed time: 2994
2016-09-01 10:25:24.282  INFO 17893 --- [           main] hello.AppRunner                          : --> User [name=Pivotal Software, Inc., blog=http://pivotal.io]
2016-09-01 10:25:24.282  INFO 17893 --- [           main] hello.AppRunner                          : --> User [name=Cloud Foundry, blog=https://www.cloudfoundry.org/]
2016-09-01 10:25:24.282  INFO 17893 --- [           main] hello.AppRunner                          : --> User [name=Spring, blog=http://spring.io/projects]

注意前两次调用是在不同的线程上(GithubLookup-2, GithubLookup-1),第三个需要等待前两个线程直到可用。为了比较不用异步特性时的时间消耗,可以尝试去掉@Async注解并再次运行。可以发现总时间消耗增加了,因为每个请求至少消耗1秒。你也可以调整Executor来增加corePoolSize属性。

Note that the first two calls happen in separate threads (GithubLookup-2, GithubLookup-1) and the third one is parked until one of the two threads became available. To compare how long this takes without the asynchronous feature, try commenting out the @Async annotation and run the service again. The total elapsed time should increase noticeably because each query takes at least a second. You can also tune the Executor to increase the corePoolSize attribute for instance.


Essentially, the longer the task takes and the more tasks are invoked simultaneously, the more benefit you will see with making things asynchronous. The trade off is handling the CompletableFuture interface. It adds a layer of indirection because you are no longer dealing directly with the results.




Congratulations! You’ve just developed an asynchronous service that lets you scale multiple calls at once.