HelloKoding

Practical coding guides

CompletableFuture Exception Handling in Java

You can use the following methods to handle exceptions in Java 8+ CompletableFuture

  • Java 8+ handle, and handleAsync
  • Java 8+ exceptionally, and Java 12+ exceptionallyAsync
  • Java 8+ whenComplete, and whenCompleteAsync

The stage created by exceptionally/exceptionallyAsync and whenComplete/whenCompleteAsync can only be executed when there’re exceptions in the previous stages, while by handle/handleAsync, it can be executed in both normal and exception cases

Let’s walk through this tutorial to explore them in more details

Use handle(BiFunction) and handleAsync(BiFunction)

handle and handleAsync are used for handling exceptions and translate completion outcomes. The stage created by them can be executed in either the previous stages have exceptions or not

They accept a BiFunction with (result, exception) as arguments received from the preceding stage, and complete its stage with the return value from BiFunction

In the following example, handleAsync received NumberFormatException caused by Integer.parseInt("s") in supplyAsync, it prints out the error message and returns Error! to the next stage thenAcceptAsync

CompletableFuture completableFuture = CompletableFuture
    .supplyAsync(() -> Integer.parseInt("s"))
    .handleAsync((result, e) -> {
        if (e != null) {
            System.out.println(e.getMessage());
            return "Error!";
        } else {
            return result + 1;
        }
    })
    .thenAcceptAsync(System.out::println);

Output

java.lang.NumberFormatException: For input string: "s"
Error!

In this example, handleAsync is still executed, although there are no exceptions in the previous stages

CompletableFuture completableFuture = CompletableFuture
    .supplyAsync(() -> 1)
    .handleAsync((result, e) -> {
        if (e != null) {
            System.out.println(e.getMessage());
            return "Error!";
        } else {
            return result + 1;
        }
    })
    .thenAcceptAsync(System.out::println);

Output

2

Use exceptionally(Function) and exceptionallyAsync(Function)

Unlike handle and handleAsync, the stage created by exceptionally and exceptionallyAsync only executed when there’re exceptions in the previous stages. Apart from that, the return value from them have to be null or the same type as the preceding stage

exceptionally and exceptionallyAsync accept a Function with (exception) as the only argument

In the below example, exceptionallyAsync received NumberFormatException caused by Integer.parseInt("s") in supplyAsync, it prints out the error message and returns 0 to the next stage thenAcceptAsync

CompletableFuture completableFuture = CompletableFuture
    .supplyAsync(() -> Integer.parseInt("s"))
    .exceptionallyAsync((e) -> {
        System.out.println(e.getMessage());
        return 0;
    })
    .thenAcceptAsync(System.out::println);

Output

java.lang.NumberFormatException: For input string: "s"
0

In this example, exceptionallyAsync is not executed, as there’re no exceptions from previous stages

CompletableFuture completableFuture = CompletableFuture
    .supplyAsync(() -> 1)
    .exceptionallyAsync((e) -> {
        System.out.println(e.getMessage());
        return 0;
    })
    .thenAcceptAsync(System.out::println);

Output

1

Use whenComplete(BiConsumer) and whenCompleteAsync(BiConsumer)

whenComplete and whenCompleteAsync accept a BiConsumer with (result, exception) as the arguments

Different from handle and handleAsync, whenComplete and whenCompleteAsync are not used for translating completion outcomes (return no value). The stage created by them are only executed when there’re exceptions in the previous stages

In the following example, whenCompleteAsync received NumberFormatException caused by Integer.parseInt("s") in supplyAsync, and prints out the error message

CompletableFuture completableFuture = CompletableFuture
    .supplyAsync(() -> Integer.parseInt("s"))
    .whenCompleteAsync((result, e) -> {
        if (e != null) System.out.println(e.getMessage());
    })
    .thenAcceptAsync(System.out::println);

Output

java.lang.NumberFormatException: For input string: "s"

In this example, whenCompleteAsync is not executed, as there’re no exceptions from previous stages

CompletableFuture completableFuture = CompletableFuture
    .supplyAsync(() -> 1)
    .whenCompleteAsync((result, e) -> {
        if (e != null) System.out.println(e.getMessage());
    })
    .thenAcceptAsync(System.out::println);

Output

1

Conclusion

In this article, we learned to handle exceptions in Java CompletableFuture by using handle, handleAsync, exceptionally, exceptionallyAsync, whenComplete and whenCompleteAsync. You can find the full source code as below

CompletableFutureExceptionHandlingTest.java

package com.hellokoding.java.concurrent;

import org.junit.Test;

import java.util.concurrent.CompletableFuture;

public class CompletableFutureExceptionHandlingTest {
    @Test
    public void handleWithException() {
        CompletableFuture completableFuture = CompletableFuture
            .supplyAsync(() -> Integer.parseInt("s"))
            .handleAsync((result, e) -> {
                if (e != null) {
                    System.out.println(e.getMessage());
                    return "Error!";
                } else {
                    return result + 1;
                }
            })
            .thenAcceptAsync(System.out::println);
    }

    @Test
    public void handleWithoutExceptions() {
        CompletableFuture completableFuture = CompletableFuture
            .supplyAsync(() -> 1)
            .handleAsync((result, e) -> {
                if (e != null) {
                    System.out.println(e.getMessage());
                    return "Error!";
                } else {
                    return result + 1;
                }
            })
            .thenAcceptAsync(System.out::println);
    }

    @Test
    public void exceptionallyWithException() {
        CompletableFuture completableFuture = CompletableFuture
            .supplyAsync(() -> Integer.parseInt("s"))
            .exceptionallyAsync((e) -> {
                System.out.println(e.getMessage());
                return 0;
            })
            .thenAcceptAsync(System.out::println);
    }

    @Test
    public void exceptionallyWithoutExceptions() {
        CompletableFuture completableFuture = CompletableFuture
            .supplyAsync(() -> 1)
            .exceptionallyAsync((e) -> {
                System.out.println(e.getMessage());
                return 0;
            })
            .thenAcceptAsync(System.out::println);
    }

    @Test
    public void whenCompleteWithException() {
        CompletableFuture completableFuture = CompletableFuture
            .supplyAsync(() -> Integer.parseInt("s"))
            .whenCompleteAsync((result, e) -> {
                if (e != null) System.out.println(e.getMessage());
            })
            .thenAcceptAsync(System.out::println);
    }

    @Test
    public void whenCompleteWithoutExceptions() {
        CompletableFuture completableFuture = CompletableFuture
            .supplyAsync(() -> 1)
            .whenCompleteAsync((result, e) -> {
                if (e != null) System.out.println(e.getMessage());
            })
            .thenAcceptAsync(System.out::println);
    }
}
Follow HelloKoding