How to Make Parallel Calls in Java Springboot Application and How to Test Them?

In the context of one of our finance projects, we faced performance issues. Some of these problems were due to multiple successive calls. Indeed, we made a lot of independent and synchronous calls. For example, we made three calls to get some information about: a client, his accounts, and his investment choices. In our case, after these calls, we used results, so we wanted to parallelize the three calls in order to improve our performance. This helped us to divide by two the execution time, which represented 600ms for each client and our sponsor enjoyed the improvements.

  • How to make parallel calls in java?
  • How to test asynchronous function?

This article will help you to implement parallel calls in a Spring Boot Java application and to test these asynchronous functions.

Prerequisites to implement asynchronous calls

Let’s start with the requirements to implement asynchronous calls. You need to have two or more independent calls to third-party API and that can be executed at the same time. Let’s take an example, you want to implement a Spring MVC resource whose goal is to provide the list of European countries whose official language is French. In this example, you need two independent calls: one to fetch all European countries and the other to fetch all countries whose official language is French. In order to have a nicer interface to use our resource, a swagger was implemented, you can find how in my code (available at the end of the article) or here. So you have the following resource, client and a country POJO (Plain Old Java Object):

The CountryClient.java (see below) client allows us to make HTTP requests in order to get countries by language and by region thanks to the API of RESTCountries.

You have an application that works but let’s suppose that the call to get all French-speaking countries is 2 seconds long and the call to get all European countries is 3 seconds long. You have to wait 5 seconds before being able to use the results instead of 3 seconds. So you want to parallelize these two independent calls. To do so, you have to do the following steps :

  1. Add @Asyncannotation to the function you want to parallelize getCountriesByLanguage and getCountriesByRegion
  2. Change the return type of the function by CompletableFuture<List<Country>>
  3. Change the return of getCountriesByLanguage and getCountriesByRegion by CompletableFuture.completedFuture(Arrays.asList(response))
  4. Change the type of what return getCountriesByLanguage and Region by CompletableFuture<List<Country>>
  5. Add a try-catch when you use the completableFuture in your resource
  6. Add a .get() in order to use the elements of the list of countries
  7. Add throws Throwable to the function getAllEuropeanFrenchSpeakingCountries
  8. Add an AsyncConfiguration

The try-catch is not necessary but it is a good reflex to have.
To recap, your new code should look like what follows :

The AsyncConfigurationfile allows us to use the use the asynchronous functions and the @Async annotation. If you want more details like how to increase the thread pool size, you can find some here.

Unit testing these functions?

Here we are, we have two parallel calls which work and we want to create some unit tests. So we need to test our client and our resource. First, to test our client, we act as if the function was not asynchronous. In this example, we use Mockito to mock the client and to get the response, we need to use .get() before testing the values.

In order to test our resource, we can mock the response of our client, let’s return France and Belgium for the French spoken countries and France and Germany for the European countries, the result of the intersection should be France. We need to return a CompletableFuture, we do exactly as if the function was not async and then return a CompletableFuture.completedFuture .

There you go! We transformed our two independent calls in two asynchronous ones.

Bibliography