Search code examples
javafuturejava.util.concurrentcompletable-future

CompletableFuture controlled way abort on exception


Let's suppose I have a CompletableFuture with a couple of stages chained:

processedFuture = someCompletableFuture
    .thenApply(<stage1>)
    .thenApply(<stage2>)

And let's assume that in case an error happens in <stage1> we would like to abort the execution of <stage2> and return SOME_DEFAULT_VALUE. Given the following options:

// Option A
val mayFailFuture = someCompletableFuture
    .thenApply(<stage1>);

if (mayFailFuture.isCompletedExceptionally()) {
    log.error(...);
    return SOME_DEFAULT_VALUE;
}

processedFuture = mayFailFuture.thenApply(<stage2>)
// Option B
processedFuture = someCompletableFuture
    .thenApply(<stage1>)              // returns CompletableFuture<T>
    .exceptionally(<exceptionally1>)  // must return CompletableFuture<T>
    .thenApply(<stage2>)

Is Option A the correct way to abort a chained execution of stages?
In Option B is there any way to abort the execution and return SOME_DEFAULT_VALUE?


Solution

  • To return your default value when stage1 completes exceptionally, you could consider using handle(BiConsumer):

    processedFuture = someCompletableFuture
        .thenApply(<stage1>)
        .handle((s1Result, s1Exception) -> {
            if(s1Exception!=null) return SOME_DEFAULT_VALUE; //Exception was thrown in S1
            else return stage2.apply(s1Result); //apply stage2 otherwise
        })
    

    Edit:

    Michael already posted a better solution in comments, which is to apply exceptionally at the end:

    processedFuture = someCompletableFuture
        .thenApply(<stage1>)
        .thenApply(<stage2>)
        .exceptionally(e -> SOME_DEFAULT_VALUE);
    

    Just bear in mind that stage2 never executes if stage1 throws an Exception, in which case that same Exception is propagated to exceptionally.

    Edit 2:

    As per comment, if you wish to return different default values depending on which stage failed, this should do the trick:

    processedFuture = someCompletableFuture
        .thenApply(<stage1>)
        .handle((s1Result, s1Exception) -> {
            if(s1Exception!=null) return DEFAULT_VALUE_1; //Exception was thrown in S1
            else return stage2.apply(s1Result); //apply stage2 otherwise
        })
        .exceptionally(e -> DEFAULT_VALUE_2); //treat exceptional return from stage2