Send Multiple Requests Concurrently in Java: Implement CompletableFuture


Asynchronous programming in Action

Modern life is fast.
We eat fast food, demand faster internet, shorter delivery times and instant news. So modern applications need to be fast.
Fast and reliable.

One way to achieve this is by using asynchronous programming.
In Asynchronous programming, main thread continues its execution without to be blocked by separate tasks that run on other threads , when they are completed , main thread is notified about their progress, completion or failure.

Asynchronous programming:
+ improves application performance
+ enhance responsiveness

Introduced in Java 8, CompletableFuture, is a way to perform Asynchronous programming , implements Future interface and provides the ability to complete a future, perform error handling , chain several futures , combine results of multiple futures .

Let’s see an example, nail your keyboards!

Assume that we have a sophisticated application that check the rates between Bitcoin and other currencies and suggest the user which currency to buy, EURO or CHF.

To implement it , I used the investing-cryptocurrency-markets API. 

We will send the 2 requests concurrently and when both responses fetched , we will check which currency have the minimum rate and will return it to the client.

So, we will send 2 CompletableFuture futureBTCtoEURO and futureBTCtoCHF to get the Rates .

final CompletableFuture<Double> futureBTCtoEURO = getRate(BTC,EURO);
final CompletableFuture<Double> futureBTCtoCHF = getRate(BTC, CHF);

getRate(int fromCurrency, int toCurrency) method, will send the request to find the rate to buy fromCurrency, toCurrency and returns a CompletableFuture. In case of exception , return as default 0d.

private CompletableFuture<Double> getRate(int fromCurrency, int toCurrency) {
 return CompletableFuture.supplyAsync(() ->     sendRequest(fromCurrency, toCurrency))
     .exceptionally(exception -> {
      System.err.println(exception);
      return 0d;
 });
}

combinedFuture is created by futureBTCtoEURO and futureBTCtoCHF , waits both features to complete

CompletableFuture<Void> combinedFuture = CompletableFuture.allOf(futureBTCtoEURO, futureBTCtoCHF);

CompletableFuture<Void> combinedFuture = CompletableFuture.allOf(futureBTCtoEURO, futureBTCtoCHF);

combinedFuture.get();

get is a blocking method so waits all futures to be completed

When they are completed, we assign on double basicBTCtoEURO and double basicBTCtoCHF the results of request.
First we validate if the futures are Completed without exception and then we update the value.

double basicBTCtoEURO = 0;
double basicBTCtoCHF = 0;

if (!futureBTCtoEURO.isCompletedExceptionally() && futureBTCtoEURO.isDone()) {
   basicBTCtoEURO = futureBTCtoEURO.get();
 }
 if (!futureBTCtoCHF.isCompletedExceptionally() && futureBTCtoCHF.isDone()) {
   basicBTCtoCHF = futureBTCtoCHF.get();
 }

To sum up:

String getBestRate() {

    int BTC = 189;
    int EURO = 17;
    int CHF = 4;

    double basicBTCtoEURO = 0;
    double basicBTCtoCHF = 0;

    final CompletableFuture<Double> futureBTCtoEURO = getRate(BTC, EURO);
    final CompletableFuture<Double> futureBTCtoCHF = getRate(BTC, CHF);

    // allOf :: waits to complete features futureBTCtoEURO,futureBTCtoCHF
    CompletableFuture<Void> combinedFuture = CompletableFuture.allOf(futureBTCtoEURO, futureBTCtoCHF);
    try {
        combinedFuture.get();//get: blocking method, waits all futures to be completed

        if (!futureBTCtoEURO.isCompletedExceptionally() && futureBTCtoEURO.isDone()) {
            basicBTCtoEURO = futureBTCtoEURO.get();
        }
        if (!futureBTCtoCHF.isCompletedExceptionally() && futureBTCtoCHF.isDone()) {
            basicBTCtoCHF = futureBTCtoCHF.get();
        }

    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
    return findMin(basicBTCtoEURO, basicBTCtoCHF);
}

Let’s run unit test:

@Test
public void sendMultipleRequests(){
    CompletableFutureImpl fut = new CompletableFutureImpl();
    String currency = fut.getBestRate();
    System.out.println( " Buy " + currency);
    Assert.assertNotNull(currency);
 }

Console logs:

Let's get started!
 getRate :fromCurrency  189 to currency 17
sending
 getRate :fromCurrency  189 to currency 4
sending
 getRate :fromCurrency  189 to currency 17 response {"data":[[{"currency_ID":17,"basic":"34910.1","reverse":"0.0000286","digits":1}]]}
 getRate :fromCurrency  189 to currency 4 response {"data":[[{"currency_ID":4,"basic":"36053.4","reverse":"0.0000277","digits":1}]]}
 Buy EURO

Check the code on Github : https://github.com/despoina555/CodeExamples/blob/main/src/main/java/org/despina/CompletableFutureImpl.java