Search code examples
javacompletable-future

Combine two futures as main and backup


I have two processes which should start in parallel and if the first process fails, I should use second process result. I came up with the following code. Is there any sensible way to simplify it?

final CompletableFuture<Data> mainFut = mainProcess(...);
final CompletableFuture<Data> backupFut = backupProcess(...);
final CompletableFuture<Data> resultFut = new CompletableFuture<>();
mainFut.handle((r, t) -> {
    if (t == null) {
        resultFut.complete(r); // main process is fine, use its result
    } else {
        LOGGER.error("Main process failed, using backup", t);
        backupFut.handle((rb, tb) -> {
            if (tb == null) {
                resultFut.complete(rb); //backup is fine, use its result
            } else {
                resultFut.completeExceptionally(tb); //backup failed too
            }
            return null;
        });
    }
    return null;
});
return resultFut;

Solution

  • You are not using the result of handle, which would be perfect for your use case, e.g.

    final CompletableFuture<Data> mainFut = mainProcess(...);
    final CompletableFuture<Data> backupFut = backupProcess(...);
    
    return mainFut.handle((r,t) -> t == null? r: backupFut.join());
    

    If you want to avoid the potentially blocking join() call, you can use

    final CompletableFuture<Data> mainFut = mainProcess(...);
    final CompletableFuture<Data> backupFut = backupProcess(...);
    
    return mainFut.handle((r1, t1) -> t1 == null? mainFut: backupFut)
        .thenCompose(Function.identity());
    

    The backup operation’s result will supersede the main operation’s exception, but when both fail, you can include the original exception in the exceptional result like

    return mainFut.handle((r1, t1) -> t1 == null? mainFut:
            backupFut.whenComplete((r2, t2) -> { if(t2 != null) t2.addSuppressed(t1); }))
        .thenCompose(Function.identity());