Enterprise Java

Call on me, or Asynchronous REST

This article is a very simple example of a working asynchronous REST application, made with Spring Boot + Java 8. Spring Boot makes developing web applications almost ridiculously easy, but to simplify the task even more, I took an example from Spring repository called rest-service , forked it to my own repository and changed it for my purposes to create two applications: a client and a server.

Our server app will be a simple REST web service that will query GitHub to get some user data and return it. Our client app will also be a REST web service… that will query the first app!

The server code basically consists of the service and a controller. The service uses an asynchronous method with the @Async annotation and looks like this.

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

}

The server controller:

@RestController
public class GitHubController {

    private final GitHubLookupService lookupService;

    @Autowired
    public GitHubController(GitHubLookupService lookupService) {
        this.lookupService = lookupService;
    }

    @RequestMapping("/user/{name}")
    public CompletableFuture<TimedResponse<User>> findUser(@PathVariable(value = "name") String name) throws InterruptedException, ExecutionException {
        long start = System.currentTimeMillis();
        ServerResponse response = new ServerResponse(Thread.currentThread().getName());
        return lookupService.findUser(name)
                .thenApply(user -> {
                    response.setData(user);
                    response.setTimeMs(System.currentTimeMillis() - start);
                    response.setCompletingThread(Thread.currentThread().getName());
                    return response;
                });
    }

}

What we have here is a simple CompletableFuture from Java 8 which we transform into the format we need with the help of thenApply() which allows us to add some data about the current thread to make sure that the execution really happens asynchronously, that is, the thread that is finishing the work is not the thread that started the work. We can make sure of it, running the application and checking the result of the call:

marina@Marinas-MacBook-Pro:~$ http http://localhost:8080/user/mchernyavskaya
HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Date: Mon, 02 Oct 2017 18:07:54 GMT
Transfer-Encoding: chunked

{
    "completingThread": "SimpleAsyncTaskExecutor-1",
    "data": {
        "avatar_url": "https://avatars2.githubusercontent.com/u/538843?v=4",
        "company": "OLX",
        "location": "Berlin, Germany",
        "name": "Maryna Cherniavska",
        "url": "https://api.github.com/users/mchernyavskaya"
    },
    "error": false,
    "startingThread": "http-nio-8080-exec-1",
    "timeMs": 2002
}

Now we need to create a client app that will be calling the server app. There’s a very convenient class for consuming REST in Spring which is called RestTemplate. However, RestTemplate is synchronous and all our nice asynchronous processing that happens in the server application would be no help to the client application at all. The two applications are completely independent. All the client app knows is that it is going to handle a rather long-running call. Since the client app knows that and since it probably doesn’t want to hog the thread for the whole time the server app is queries, we are going to make it asynchronous as well. AsyncRestTemplate coming to the rescue!

Our client application will be even more simple and will mainly consist of the controller code. To run both applications on one local machine, we need to change ports for the server with the -Dserver.port=8082 parameter. So, our server is now on localhost:8080 and the client is on localhost:8082.

The client controller is mainly as follows.

@RestController
public class GitHubController {
    private static final String BASE_URL = "http://localhost:8080/";

    private final AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate();

    @RequestMapping("/async/user/{name}")
    public ListenableFuture<ClientResponse> findUserAsync(@PathVariable(value = "name") String name)
            throws InterruptedException, ExecutionException {
        long start = System.currentTimeMillis();
        ClientResponse clientResponse = new ClientResponse(Thread.currentThread().getName());
        ListenableFuture<ResponseEntity<ServerResponse>> entity = asyncRestTemplate.getForEntity(BASE_URL + name, ServerResponse.class);
        entity.addCallback(new ListenableFutureCallback<ResponseEntity<ServerResponse>>() {
            @Override
            public void onFailure(Throwable ex) {
                clientResponse.setError(true);
                clientResponse.setCompletingThread(Thread.currentThread().getName());
                clientResponse.setTimeMs(System.currentTimeMillis() - start);
            }

            @Override
            public void onSuccess(ResponseEntity<ServerResponse> result) {
                clientResponse.setData(result.getBody());
                clientResponse.setCompletingThread(Thread.currentThread().getName());
                clientResponse.setTimeMs(System.currentTimeMillis() - start);
            }
        });

    }
}

We are taking the server response and wrapping it into more data about the timing and current threads to better see what is going on. The AsyncRestTemplate gives us a ListenableFuture, but we make a CompletableFuture out of it because it allows us to manually control the moment when the future returns and also transform the output in the process.

When we call the client service, it returns the following data:

marina@Marinas-MacBook-Pro:~$ http http://localhost:8082/async/user/mchernyavskaya
HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Date: Mon, 02 Oct 2017 18:28:36 GMT
Transfer-Encoding: chunked

{
    "completingThread": "SimpleAsyncTaskExecutor-1",
    "data": {
        "completingThread": "SimpleAsyncTaskExecutor-3",
        "data": {
            "avatar_url": "https://avatars2.githubusercontent.com/u/538843?v=4",
            "company": "OLX",
            "location": "Berlin, Germany",
            "name": "Maryna Cherniavska",
            "url": "https://api.github.com/users/mchernyavskaya"
        },
        "error": false,
        "startingThread": "http-nio-8080-exec-7",
        "timeMs": 1403
    },
    "error": false,
    "startingThread": "http-nio-8082-exec-3",
    "timeMs": 1418
}

You can read more about asynchronous methods in Spring here but this simple example should help you understand how things work. The full code is in the repository. Hope it is of some use!

Published on Java Code Geeks with permission by Maryna Cherniavska, partner at our JCG program. See the original article here: Call on me, or Asynchronous REST

Opinions expressed by Java Code Geeks contributors are their own.

Maryna Cherniavska

Maryna has productively spent 10+ years in IT industry, designing, developing, building and deploying desktop and web applications, designing database structures and otherwise proving that females have a place among software developers. And this is a good place.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

8 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Patrik Bergman
Patrik Bergman
6 years ago

Very good and well written example, thanks!

Sam Gandhi
Sam Gandhi
5 years ago

The example works fine. How do we send an HTTP 202 response in this example to the client if we know that we have a long running process and the actual response will take time? Thanks

David McManamon
David McManamon
4 years ago

If the starting thread & completing thread have different names is that enough to know the async worked asynchronously? It would be nice to see thread numbers higher than 1.

David McManamon
David McManamon
4 years ago

Yes, that was really my question. Is it possible to get asynchronous results without creating a task executor bean? I don’t think it is but I’ll clone and try tomorrow. Thanks for responding.

Adrian
Adrian
3 years ago

I still don’t see the benefit of returning a CompletableFuture on the REST endpoint. Why not just increase the number of web server threads (Tomcat threads) to handle more concurrent events? With Asynchronous REST, we are just offloading work to the worker thread pool, and assuming the worker thread pool is a fixed size, we will end up being limited to the number of worker threads. Please explain the benefit to me..

Back to top button