Reputation: 1402
I often find library methods which return CompletableFuture, and also have throws Exception
in the function signature. As I understand it, exceptions thrown within these functions will not be caught by a try-catch since the future typically does not complete until long after the code has left the try-catch block. For this reason, we're told to attach an .exceptionally()
to the CompletableFuture chain to handle these failures.
So I would like to be able to just write this:
CompletableFuture<Void> libFunction() throws Exception;
CompletableFuture<Void> caller() {
return libFunction()
.exceptionally(exception -> {
// code to handle exception
})
}
The problem is that java requires caller() to either have a try-catch block wrapped around the libFunction invocation, or have caller() include throws Exception
in its signature. If I include a try-catch, it amounts to writing some useless exception handling code which can never get called, and is redundant to the code in the exceptionally block.
The other option is to add throws Exception
to my function signature. But that means every function in the call chain, until .join() is called, needs to have throws Exception
in it's signature. And afaict, the function could never actually throw an exception because if it did, CompletableFuture would wrap the exception in a CompletionException, and pass it to .exceptionally.
So my question is how do I write good asynchronous code using CompletableFuture that doesn't require either unnecessary exceptions being added to all my function signatures or redundant try-catch blocks just to satisfy the compiler?
Update: A bit more context - I work with a framework which auto-generates signatures associated with gRPC service routes. These signatures themselves return CompletionStage. So technically the join is getting called by the framework, not by me. So it's not practical to just bubble up the Exception to where the join takes place since the top level function signature is autogenerated.
Upvotes: 0
Views: 1421
Reputation: 1402
This is all cleared up now thanks to @rogue's answer. The way to think about this is that any function returning a completable future has both a synchronous and an asynchronous component. If the former throws an exception, it should be caught with a try-catch block. If the latter throws an exception, it's wrapped in a CompletionException, returned, and passed as the argument to the exceptionally block. So having both a try-catch and an exceptionally block is not redundant.
Thanks to both @rogue and @david-conrad for helping me understand this.
Upvotes: 0