Skip to Main Content

Java APIs

Announcement

For appeals, questions and feedback, please email oracle-forums_moderators_us@oracle.com

Exception handling proposals in CompletionStage/CompletableFuture

Joris GuffensJan 12 2024 — edited Jan 12 2024

I've struggled with this a few times in the past years. There are much more methods for normal completion as opposed to exceptional completion in the CompletionStage interface.

I just want to handle an exceptional completion from a previous stage in the chain. For a problem that should be trivial, the options are:

doSomethingAsync()
    .thenAccept(result -> handleResult(result))
    
    // option 1
    .exceptionally(throwable -> {
        LOGGER.error(throwable);
        return null; 
    })
    
    // option 2
    .whenComplete((result, throwable) -> {
        if ( throwable != null ) {
            LOGGER.error(throwable);
        }
    })
    
    // option 3
    .handle((result, throwable) -> {
        if ( throwable != null ) {
            LOGGER.error(throwable);
        }
        return null;
    })

For reference of these methods: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/CompletionStage.html

Option 1
I have to return a value for the new stage to complete normally. What if I don't have a meaningful fallback value or I don't want to continue completing normally?

Option 2
I have to check if the stage completed exceptionally. In this case, this is too verbose for something that should be trivial. I also don't have any use for the result parameter.

Option 3
Has both the issues of option 1 and option 2.

Additionally, I have seen some smelly things happen which could have been avoided with a better API. For example when the last stage of the chain is:

.whenComplete((result, throwable) -> {
    if ( throwable != null ) {
        LOGGER.error(throwable);
    }
    handleResult(result); // <- what if an exception occurs here? This will not be handled.
})

Proposed solutions
Just as with .thenApply(…) and .thenAccept(…) I think that .exceptionally(…) should respectively have been .exceptionallyApply(…) and .exceptionallyAccept(…). Continuing normal completion without a value is a valid use case.

But then again, what if you don't want to continue normal completion? There might be future stages that expect a non-null value and you can't provide a meaningful fallback, you want to continue the exceptional flow. That's why I also suggest .whenExceptionally(…) which behaves the same as .whenComplete(…) but accepts a Consumer with only the throwable parameter. (It might also be nice to add a similar method for normal completion)

Please let me know what you think!

Comments
Post Details
Added on Jan 12 2024
1 comment
41 views