Make the API multiple times with AsyncRestTemplate and wait for all to complete
I need to make a Rest API call using RestTemplate multiple times with different parameters. The API is the same, but it's a parameter that changes. The number of times is also variable. I want to use AsyncRestTemplate, but my main thread has to wait for all API calls to complete. I also want to work with the responses returned by each API call. I am currently using RestTemplate. In its basic form, this is so.
List<String> listOfResponses = new ArrayList<String>();
for (Integer studentId : studentIdsList) {
String respBody;
try {
ResponseEntity<String> responseEntity = restTemplate.exchange(url, method, requestEntity, String.class);
} catch (Exception ex) {
throw new ApplicationException("Exception while making Rest call.", ex);
}
respBody = requestEntity.getBody();
listOfResponses.add(respBody);
}
How can I implement AsyncRestTemplate in this situation?
source to share
The basic idea when using AsyncRestTemplate
(or any asynchronous API) is to send all your requests the first time, saving the corresponding futures, and then process all responses a second time. You can simply do it with 2 loops:
List<ListenableFuture<ResponseEntity<String>>> responseFutures = new ArrayList<>();
for (Integer studentId : studentIdsList) {
// FIXME studentId is not used
ListenableFuture<ResponseEntity<String>> responseEntityFuture = restTemplate.exchange(url, method, requestEntity, String.class);
responseFutures.add(responseEntityFuture);
}
// now all requests were send, so we can process the responses
List<String> listOfResponses = new ArrayList<>();
for (ListenableFuture<ResponseEntity<String>> future: responseFutures) {
try {
String respBody = future.get().getBody();
listOfResponses.add(respBody);
} catch (Exception ex) {
throw new ApplicationException("Exception while making Rest call.", ex);
}
}
Note: if you need to concatenate the responses with the original requests, you can replace the futures list with a map or the request + response object list.
I also noted that it is studentId
not used in your question.
source to share
You can use Java 8 Stream API if possible for you:
List<String> listOfResponses = studentIdsList.stream()
.parrallel()
.map({studentId ->
ResponseEntity<String> responseEntity = restTemplate.exchange(url, method, studentId, String.class);
return responseEntity.getBody();
})
.collect(Collectors.toList());
This code will basically do 2 things:
- Executes queries in parallel;
- Collect the query results into a list.
UPDATE: Agree with @Didier L - this solution may not work as expected when you need a lot of queries. Here's an updated version:
List<String> listOfResponses = studentIdsList.stream()
.map(studentId -> asyncRestTemplate.exchange(url, method, studentId, String.class)
.collect(Collectors.toList()).stream()
.map(this::retrieveResult)
.collect(Collectors.toList());
/**
* Retrieves results of each request by blocking the main thread. Note that the actual request was performed on the previous step when
* calling asyncRestTemplate.exchange(url, method, studentId, String.class)
*/
private String retrieveResult(ListenableFuture<ResponseEntity<String>> listenableFuture) {
try {
return listenableFuture.get().getBody();
} catch (Exception e) {
e.printStackTrace();
}
}
source to share
Here is another solution I would like to suggest which uses Spring RestTemplate and not AsyncRestTemplate. It also uses Java 8 CompletingFuture.
public void sendRequestsAsync(List<Integer> studentList) {
List<CompletableFuture<Void>> completableFutures = new ArrayList<>(studentList.size()); //List to hold all the completable futures
List<String> responses = new ArrayList<>(); //List for responses
ExecutorService yourOwnExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
for (Integer studentId : studentList) { //Iterate student list
CompletableFuture<Void> requestCompletableFuture = CompletableFuture
.supplyAsync(
() -> restTemplate.exchange("URL/" + studentId, HttpMethod.GET, null, String.class),
yourOwnExecutor
)//Supply the task you wanna run, in your case http request
.thenApply((responseEntity) -> {
responses.add(responseEntity.getBody());
return responseEntity;
})//now you can add response body to responses
.thenAccept((responseEntity) -> {
doSomeFinalStuffWithResponse(responseEntity);
})//here you can do more stuff with responseEntity (if you need to)
.exceptionally(ex -> {
System.out.println(ex);
return null;
});//do something here if an exception occurs in the execution;
completableFutures.add(requestCompletableFuture);
}
try {
CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[completableFutures.size()])).get(); //Now block till all of them are executed by building another completablefuture with others.
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
I like this solution more because I can tie in as much business logic as I want and shouldn't depend on Spring internals for Async Sending. Obviously, you can clean up the code more, I haven't paid much attention to this yet.
source to share