Java – CompletableFuture handle and completeExceptionally cannot work together

completable-futureexception handlingjava

I am trying to set a default value when exceptions happen in CompletableFuture I made it work by handle method as follows:

private static void testHandle() {
    String name = null;
    CompletableFuture<String> completableFuture
            = CompletableFuture.supplyAsync(() -> {
        if (name == null) {
            throw new RuntimeException("Computation error!");
        }
        return "Hello, " + name;
    }).handle((s, t) -> s != null ? s : "Hello, Stranger!" + t.toString());
    out.println(completableFuture.join());
}

But when I tried to stop the CompletableFuture using completeExceptionally when bad things happen and track the exception as follows I cannot catch the exception as I did just now.

private static void testCompleteExceptionally() {
    String name = "Hearen";
    CompletableFuture<String> completableFuture
            = CompletableFuture.supplyAsync(() -> {
        delay(500L);
        if (name == null) {
            throw new RuntimeException("Computation error!");
        }
        return "Hello, " + name;
    }).handle((s, t) -> {
        try {
            throw t.getCause(); 
        } catch (Throwable e) {
            out.println(e.toString()); // I was hoping to record the custom exceptions here;
        }
        return s != null ? s : "Hello, Stranger!" + t.toString();
    });

    if (name != null) {
        completableFuture.completeExceptionally(new RuntimeException("Calculation failed!")); // when bad things happen, I try to complete it by exception;
    }
    out.println(completableFuture.join());

}

UPDATED 2018-06-09 Thanks for the help, @Daniele

private static void testCompleteExceptionally() {
    String name = "Hearen";
    CompletableFuture<String> completableFuture
            = CompletableFuture.supplyAsync(() -> {
        delay(500L);
        if (name == null) {
            throw new RuntimeException("Computation error!");
        }
        return "Hello, " + name;
    });

    if (name != null) {
        completableFuture.completeExceptionally(new RuntimeException("Calculation failed!"));
    }
    out.println(completableFuture.handle((s, t) ->  s != null ? s : "Hello, Stranger!" + t.toString()).join());

}

The handle enclosed just before join() works as expected. But in this case, the returned value will be null.

Based on the handle API

Returns a new CompletionStage that, when this stage completes either normally or exceptionally, is executed with this stage's result and exception as arguments to the supplied function.

Best Answer

You are building a future, piping with an handle (so getting another future) and then completing exceptionally the future returned by the handle.

You should complete exceptionally the inner future itself, instead of the handle.

The point here is that handle returns another future; and you should not complete the "outer" future exceptionally, because doing that will bypass the handling behavior.

Below the code;

package stackOv;

import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import java.util.function.Supplier;

public class TestHandle {
  BiFunction<String, Throwable, String> handle2 = new BiFunction<String, Throwable, String>() {
    @Override
    public String apply(String s, Throwable t) {
      try {
        throw t.getCause(); 
      } catch (Throwable e) {
        // I was hoping to record the custom exceptions here;
        System.out.println(e.toString());
      }
      return s != null ? s : "Hello, Stranger!" + t.toString();
    }
  };

  private void testCompleteExceptionally() {
    String name = "Hearen";
    Supplier<String> supplier2 = () -> {
      delay(500L);
      if (name == null) {
        throw new RuntimeException("Computation error!");
      }
      return "Hello, " + name;
    };
    CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(supplier2);

    if (name != null) {
      // when bad things happen, I try to complete it by exception;
      completableFuture.completeExceptionally(new RuntimeException("Calculation failed!"));      
    }
    System.out.println(completableFuture.handle(handle2).join());
  }

  public static void main(String[] args) {
    TestHandle th = new TestHandle();
    th.testCompleteExceptionally();
  }

  private static void delay(long milli) {
    try { Thread.sleep(milli); } catch (InterruptedException e) {}    
  }
}