20

I wrote the following code to test the performance of both the sync RestTemplate and AsyncRestTemplate. I just ran it a few times manually on POSTMAN.

We are just passing 10 references into a GET call so that we can return 10 links:

RestTemplate - synchronous and returns in 2806ms:

ArrayList<String> references = new ArrayList<>();
ArrayList<String> links = new ArrayList<>();
RestTemplate restTemplate = new RestTemplate(); 
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
for (int i = 0; i < 10; i++) {
    ResponseEntity<String> resource = restTemplate.getForEntity(references.get(i), String.class);
    links.add(resource.getBody().toString());
}

RestTemplate - asynchronous and returns in 2794ms:

//Creating a synchronizedList so that when the async resttemplate returns, there will be no concurrency issues
List<String> links = Collections.synchronizedList(new ArrayList<String>());

//CustomClientHttpRequestFactory just extends SimpleClientHttpRequestFactory but disables automatic redirects in SimpleClientHttpRequestFactory
CustomClientHttpRequestFactory customClientHttpRequestFactory = new CustomClientHttpRequestFactory();
//Setting the ThreadPoolTaskExecutor for the Async calls
org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor pool = new org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor();
pool.setCorePoolSize(5);
pool.setMaxPoolSize(10);
pool.setWaitForTasksToCompleteOnShutdown(true);
pool.initialize();
//Setting the TaskExecutor to the ThreadPoolTaskExecutor
customClientHttpRequestFactory.setTaskExecutor(pool);

ArrayList<String> references = new ArrayList<>();
ArrayList<String> links = new ArrayList<>();
AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate(customClientHttpRequestFactory); 
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
for (int i = 0; i < 10; i++) {
    Future<ResponseEntity<String>> resource = asyncRestTemplate.getForEntity(references.get(i), String.class);
    ResponseEntity<String> entity = resource.get(); //this should start up 10 threads to get the links asynchronously
    links.add(entity.getBody().toString());
}

In most cases, both methods actually return back the results with a very similar time, averaging 2800ms in both async and sync calls.

Am I doing something incorrect as I would have expected the async call to be much faster?

Marko Previsic
  • 1,820
  • 16
  • 30
Simon
  • 19,658
  • 27
  • 149
  • 217
  • it's very naive to think that "async" means faster. it has a chance only with correct lib implementation and high load (multiple clients). in other cases, it's only about API – Yura Jan 19 '22 at 12:53

3 Answers3

21

Nowadays, AsyncRestTemplate is @Deprecated in favor of WebClient. So nobody should use that class anymore!

https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/reactive/function/client/WebClient.html

membersound
  • 81,582
  • 193
  • 585
  • 1,120
18

I would say that you're missing the real benefits of the AsyncRest here. You should add callbacks to each requests you're sending so that the response will be processes only when available.

Indeed, the getForEntity method of an AsyncRestTemplate returns a ListenableFuture to which you can connect a callback task. See the official doc ListenableFuture for further information.

For example in your case it could be:

for (int i = 0; i < 10; i++) {
     ListenableFuture<ResponseEntity<String>> response = asyncRestTemplate.getForEntity(references.get(i), String.class);
     response.addCallback(new ListenableFutureCallback<ResponseEntity<String>>() {
            @Override
            public void onSuccess(ResponseEntity<String> result) {
                // Do stuff onSuccess 
                links.add(result.getBody().toString());
            }

            @Override
            public void onFailure(Throwable ex) {
                log.warn("Error detected while submitting a REST request. Exception was {}", ex.getMessage());
            }
        });
}
Ugo Giordano
  • 373
  • 3
  • 11
9

The tricky thing with Java Future is that it's not composable and it's really easy to block.

In this case, calling future.get() makes your code block and wait until the response is back. In fact, this approach makes sequential calls and does not leverage the async nature of this RestTemplate implementation.

The simplest way to fix this is to separate it in two loops:

ArrayList<Future<ResponseEntity<String>>> futures = new ArrayList<>();

for (String url : references.get()) {
    futures.add(asyncRestTemplate.getForEntity(url, String.class)); //start up to 10 requests in parallel, depending on your pool
}

for (Future<ResponseEntity<String>> future : futures) {
    ResponseEntity<String> entity = future.get(); // blocking on the first request
    links.add(entity.getBody().toString());
}

Obviously there are more elegant solutions, especially if using JDK8 streams, lambdas and ListenableFuture/CompletableFuture or composition libraries.

Brian Clozel
  • 56,583
  • 15
  • 167
  • 176
  • Hi, thanks for your response. I do wonder though when we call ResponseEntity entity = future.get() in your code, doesn't that also block the code so that the for loop will not proceed until the response is received? I can see a marginal improvement in the time where the call returns in 2500ms or so but it is not substantial. – Simon Jul 23 '15 at 20:05
  • 2
    yes, future.get() blocks but at that point all requests are already sent. If you can use JDK8 CompletableFutures or other composition library, you could have something more efficient. When measuring this, keep in mind that creating a RestTemplate/AsyncRestTemplate takes times and resources and should be done once (and should not count in your timer) – Brian Clozel Jul 24 '15 at 08:12