How to combine the result of execution of two CompletableFutures?

75 Views Asked by At

A microservice of ours is calling an endpoint of a partner microservice. But because of changes in the partner microservice, now the result, that was provided by one endpoint, will be provided, when calling two different endpoints. That is why I have to separate the call into two calls and combine the results.

So I defined two methods:

    @Async("executor")
    public CompletableFuture<List<DashboardItem>> getDashboardItemsFuture1()

    @Async("executor")
    public CompletableFuture<List<DashboardItem>> getDashboardItemsFuture2()

and inside each of these methods I execute a rest call and provide the result to the CompletableFuture instance like this:

    @Async("executor")
    public CompletableFuture<List<DashboardItem>> getDashboardItemsFuture1() {
       return CompletableFuture.completedFuture(executeRestCall())
    }

And I would like to combine the results from the two CompletableFutures in a way that :

  1. It is waited till all the REST calls end their execution.
  2. If one of the REST calls execution throws Exception (for example TimeoutException) still the result of the other REST call is returned from our microservice.

So my code for this logic is :

       var dashboardItemsFuture1 = getDashboardItemsFuture1();
       var dashboardItemsFuture2 = getDashboardItemsFuture1()

       var combinedFuture = CompletableFuture.allOf(dashboardItemsFuture1, dashboardItemsFuture2);


        try {
            combinedFuture.get(); // Wait for both futures to complete
        } catch (Exception exc) {
            // Handle exceptions
            log.error(exc);
        }

        Optional.ofNullable(dashboardItemsFuture1.get())
                .ifPresent(dashboardItems::addAll);

        Optional.ofNullable(dashboardItemsFuture2.get())
                .ifPresent(dashboardItems::addAll);

But obviously this will not work, as when I simulated in a UnitTest throwing an Exception during the REST call of the first CompletableFuture, the Exception was thrown only at this line:

var dashboardItemsFuture1 = getDashboardItemsFuture1();

So - what is the correct way to combine the two CompletableFutures in the way that I want?

1

There are 1 best solutions below

0
Jean-Baptiste Yunès On

If I understand well your problem then you can use exceptionnally:

public CompletableFuture exceptionally(Function<Throwable,? extends T> fn)

Returns a new CompletableFuture that is completed when this CompletableFuture completes, with the result of the given function of the exception triggering this CompletableFuture's completion when it completes exceptionally; otherwise, if this CompletableFuture completes normally, then the returned CompletableFuture also completes normally with the same value. Note: More flexible versions of this functionality are available using methods whenComplete and handle.

// Modify futures so that in case of an Exception an empty list is returned
Function<Throwable,List<DashboardItem>> f = t -> new ArrayList<>();
var future1 = getDashboardItemsFuture1().exceptionnally(f);
var future2 = getDashboardItemsFuture2().exceptionnally(f);

// Combine both to construct a list with both lists as the result
var combined = future1.thenCombine(future2,(r1,r2) -> {
    var r = new ArrayList<DashboardItem>>(r1);
    r.addAll(r2);
    return r;
});

// get the result
var dashboardItems = combined.get();