Close

Java CompletableFuture - Exception Handling

[Last Updated: Apr 3, 2019]

This tutorial shows how to handle exceptions with CompletableFuture.

Default Exception handling

If an exception occurs in a stage and we do not do anything to handle that exception then execution to the further stages is abandon. In this case no exception is thrown by Java unless we call get() or join() methods. On calling these methods CompletionException is thrown which wraps the actual exception as the root cause exception.

Also we can use CompletableFuture.isCompletedExceptionally() method to determine if a CompletableFuture completed with an exception.
Let see an example to understand that.

package com.logicbig.example;

import java.util.concurrent.CompletableFuture;

public class Example1NoExceptionHandling {
  public static void main(String[] args) throws Exception {
      System.out.println("-- running CompletableFuture --");
      CompletableFuture<Void> completableFuture = CompletableFuture
              .supplyAsync(() -> {
                  System.out.println("running task");
                  return 1 / 0;
              })
              .thenApply(input -> {
                  System.out.println("multiplying by 2");
                  return input * 2;
              })
              .thenAccept(System.out::println);

      Thread.sleep(3000);//let the stages complete
      System.out.println("-- checking exceptions --");
      boolean b = completableFuture.isCompletedExceptionally();
      System.out.println("completedExceptionally: " + b);
      System.out.println("-- calling join --");
      completableFuture.join();
  }
}
-- running CompletableFuture --
running task
-- checking exceptions --
completedExceptionally: true
-- calling join --
Exception in thread "main" java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
        at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:314)
        at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:319)
        at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1702)
        at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1692)
        at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
        at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
        at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
        at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
        at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)
Caused by: java.lang.ArithmeticException: / by zero
        at com.logicbig.example.Example1NoExceptionHandling.lambda$main$0(Example1NoExceptionHandling.java:11)
        at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1700)
        ... 6 more



To handle exception (other than using try/catch), CompletableFuture API provides special chain methods. Let's see what are those methods.

Using exceptionally() method

CompletionStage<T> exceptionally(Function<Throwable,? extends T> fn)

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 CompletionException which wraps the actual exception as its root cause.

exceptionally() stage can be used to provide a replacement result.

Examples

package com.logicbig.example;

import java.util.concurrent.CompletableFuture;

public class Example2Exceptionally {
  public static void main(String[] args) throws Exception {
      System.out.println("-- running CompletableFuture --");
      CompletableFuture<Integer> completableFuture = CompletableFuture
              .supplyAsync(() -> {
                  System.out.println("running task");
                  return 10 / 0;
              }).exceptionally(exception -> {
                  System.err.println("exception: " + exception);
                  return 1;
              });
      Thread.sleep(3000);//let the stages complete
      System.out.println("-- checking exceptions --");
      boolean b = completableFuture.isCompletedExceptionally();
      System.out.println("completedExceptionally: " + b);
      System.out.println("-- getting results --");
      int result = completableFuture.get();
      System.out.println(result);
  }
}
-- running CompletableFuture --
running task
exception: java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
-- checking exceptions --
completedExceptionally: false
-- getting results --
1

Various chained stages:

public class Example3Exceptionally {
  public static void main(String[] args) throws Exception {
      runTasks(2);
      runTasks(0);
  }

  private static void runTasks(int i) {
      System.out.printf("-- input: %s --%n", i);
      CompletableFuture
              .supplyAsync(() -> {
                  return 16 / i;
              }).exceptionally(exception -> {
                     System.out.println("in exceptionally");
                     System.err.println(exception);
                    return 1;})
              .thenApply(input -> input * 3)
              .thenAccept(System.out::println);
  }
}

-- input: 2 --
24
-- input: 0 --
in exceptionally
java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
3

Following example shows that if a stage terminates with an exception, then all further dependent stages complete exceptionally as well, until an exceptionally stage is encountered.

package com.logicbig.example;

import java.util.concurrent.CompletableFuture;

public class Example4Exceptionally {
  public static void main(String[] args) throws Exception {
      runTasks(2);
      runTasks(0);
  }

  private static void runTasks(int i) {
      System.out.printf("-- input: %s --%n", i);
      CompletableFuture.supplyAsync(() -> 16 / i)
                       .thenApply(input -> input + 1)
                       .thenApply(input -> input + 3)
                       .exceptionally(exception -> {
                           System.out.println("in exceptionally");
                           System.err.println(exception);
                           return 1;
                       })
                       .thenApply(input -> input * 3)
                       .thenAccept(System.out::println);
  }
}
-- input: 2 --
36
-- input: 0 --
in exceptionally
java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
3

Async exception handling

Java 12 introduced exceptionallyAsync() methods. Check out examples here.

Composing Exceptionally

Java 12 also introduced exceptionallyCompose() methods. Check out examples here.

Using handle() methods

CompletionStage also defines following methods for exception handling:

<U> CompletionStage<U> handle(BiFunction<? super T,Throwable,? extends U> fn)
<U> CompletionStage<U> handleAsync(BiFunction<? super T,Throwable,? extends U> fn)
<U> CompletionStage<U> handleAsync(BiFunction<? super T,Throwable,? extends U> fn, Executor executor)

The stage created by handle() method is always executed regardless of exception occurs or not, whereas exceptionally() stage is only executed when there's an exception.

Also handle() method's BiFunction is called with a null exception if the previous CompletionStage completes normally, or it is called with a null computation result if it completes exceptionally.

Let's see an example to understand how to use handle() method:

package com.logicbig.example;

import java.util.concurrent.CompletableFuture;

public class Example5Handle {
  public static void main(String[] args) throws Exception {
      runTasks(2);
      runTasks(0);
  }

  private static void runTasks(int i) {
      System.out.printf("-- input: %s --%n", i);
      CompletableFuture
              .supplyAsync(() -> {
                  return 16 / i;
              }).handle((input, exception) -> {
          if (exception != null) {
              System.out.println("exception block");
              System.err.println(exception);
              return 1;
          } else {
              System.out.println("increasing input by 2");
              return input + 2;
          }})
              .thenApply(input -> input * 3)
              .thenAccept(System.out::println);
  }
}
-- input: 2 --
increasing input by 2
30
-- input: 0 --
exception block
java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
3

Example Project

Dependencies and Technologies Used:

  • JDK 11
  • Maven 3.5.4

Java CompletableFuture - Exception Handling Example Select All Download
  • java-completable-future-exception-handling
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • Example2Exceptionally.java

    See Also