Different ways of Handling Exceptions in CompletableFutures

Reading Time: 3 minutes

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

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):

CompletableFuture in Java

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.

CompletableFuture in Java

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


After reading this blog , you must have got the basic idea how to handle exception from completable futures with various basic exceptionally functions.



Written by 

Munander is a Software Consultant in Knoldus Software LLP. He has done b.tech from IMS Engineering college, Ghaziabad. He has decent knowledge of C,C++,Java,Angular and Lagom. He always tries to explore new technologies. His hobbies include playing cricket and adventure.

1 thought on “Different ways of Handling Exceptions in CompletableFutures4 min read

Comments are closed.