
Hi Folks! As part of this blog, we will explore how we can handle exception in the different computing stages when we use CompletableFuture. We assume the read has the basic idea of the CompletableFuture. Nonethless, will start with the basic introduction of the CompletableFuture.
What is CompletableFuture
A CompletableFuture is used for asynchronous programming which was introduced as an improvement of the java Future API in Java 8. If you are familiar with the Java’s Future API, you will be pretty much able to relate the this API. It offers composibility on different stages of computations. If you are new to asynch programming in Java, I would like you to go through this blog which explains well about the CompletableFuture API.
A CompletableFuture is a class in Java. It belongs to java.util.cocurrent package. It implements CompletionStage and Future interface.
This is the most basic completable future we have by using no arg constructor
CompletableFuture<String> CompletableFuture = new CompletableFuture<String>();
Having said that, let us explore how we handle exceptions with the different stages of CompletableFuture computation.
Exception Handling of CompletableFuture
Consider the following figure, which represents the five CFs (CompletableFutures):



Suppose Five CFs in execution and CF21 raises an exception then all the depending CF (CF31 and CF41) are in error. It means that:
- The call to isCompletedExceptionally() method returns true.
- The call to get() throws an ExecutionException which causes the root Exception.
Consider the following figure, in which we have created CF30 with an exception.



When CF21 executes normally, then CF30 just transmits the value. If it raises an exception, CF30 handles it and generate value for CF31.
Using different exception Handling Methods
There are three main method to handle an exception offered by the API as shown below. Let us go through them one by one.
public CompletableFuture <T> exceptionally(Function <Throwable, ? extends T> function);
public <U> CompletableFuture<U> hadle(BiFunction<? super T, Throwable, ? extends U> bifunction);
public CompletableFuture<T> whenComplete(BiConsumer<? super T, ? super Throwable> action);
Using exceptionally() method
This method returns a new CompletionStage
that, when this stage completes with exception, is executed with this stage’s exception as the argument to the supplied function. Otherwise, if this stage completes normally, then the returned stage also completes normally with the same value.
Simply if there’s no exception then exceptionally( ) stage is skipped otherwise it is executed.
The exception passed to the exceptionally function is Completion Exception which wraps the actual exception as its root cause.
future = future.thenApply(Integer::parseInt) // input String: "Example"
.thenApply(r -> r * 4)
.thenApply(s -> "apply>> " + s)
.exceptionally(ex -> "Error: " + ex.getMessage());
Run the code, the result will be:
Error: java.lang.NumberFormatException: For input string: "Example"
"Example" is the String we return to future object before call the first thenApply() method.
Using handle() method
future = future.thenApply(Integer::parseInt) // input String: "Example"
.thenApply(r -> r * 2)
.thenApply(s -> "apply>> " + s)
// .exceptionally(ex -> "Error: " + ex.getMessage())
.handle((result, ex) -> {
if (result != null) {
return result;
} else {
return "Error handling: " + ex.getMessage();
}
});
Run the code, the result will be:
accept: Error handling: java.lang.NumberFormatException: For input string: "Example"
Using whenComplete()
Handle() methods are allowed to return a result (in case of exception a recovering result) thus they can handle the exception. On the other hand, whenComplete()
methods cannot return a results. So they are used as merely callbacks that do not interfere in the processing pipeline of CompletionStages.
If there’s an unhandled exception coming from the stages before ‘whenComplete’ stage then that exception is passed through as it is. In other words if the upstream CompletionStage completes exceptionally, the CompletionStage returned by whenComplete() also completes exceptionally.
CompletableFuture<String> test=new CompletableFuture<>();
test.whenComplete((result, ex) -> System.out.println("stage 2: "+result+"\t"+ex))
.exceptionally(ex -> { System.out.println("stage 3: "+ex); return ""; });
test.completeExceptionally(new IOException());
will print:
stage 2: null java.io.IOException
stage 3: java.util.concurrent.CompletionException: java.io.IOException
Conclusion
After reading this blog , you must have got the basic idea how to handle exception from completable futures with various basic exceptionally functions.
Reference
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html



Nice blog on Exceptional Handling with Completable Futures in Java.