Java 12 - Exception Handling in CompletionStage (CompletableFuture)
Java 12 added five new methods to CompletionStage interface. These methods related to error recovery and are add-ons to the existing method exceptionally(Function<Throwable,T>), which recovers from a failed computation by turning the exception into a normal result. As class that implementing CompletionStage, we can have this enhancements through CompletableFuture.
CompletionStage::exceptionally
- CompletionStage<T> exceptionally(Function<Throwable,? extendsT> fn): Returns a new CompletionStage that, when this stage completes exceptionally, is executed with this stage's exception as the argument to the supplied function.
Here a sample code using existing exceptionally method in CompletableFuture:
import java.util.concurrent.CompletableFuture;
public class CompletableFutureExample {
public static void main(String[] args) throws Exception {
printWithThread("Start CompletableFutureExample...");
CompletableFuture.supplyAsync(() -> {
printWithThread("Inside supplyAsync");
if (System.currentTimeMillis() % 2 == 0) {
throw new RuntimeException("Even time..."); // 50% chance to fail
}
return "Winter is Coming!";
}).thenAcceptAsync(s -> {
printWithThread("thenAcceptAsync: " + s);
}).exceptionally(e -> {
printWithThread("exceptionally: " + e.getMessage());
return null;
});
Thread.sleep(500); // waiting for full response
printWithThread("...End");
}
private static void printWithThread(String desc) {
System.out.printf("[%s] - %s%n", Thread.currentThread().getName(), desc);
}
}
If it's odd number, no RuntimeException raised, everything work as per "normal":
[main] - Start CompletableFutureExample... [ForkJoinPool.commonPool-worker-3] - Inside supplyAsync [ForkJoinPool.commonPool-worker-3] - thenAcceptAsync: Winter is Coming! [main] - ...End
But if it's even number, RuntimeException raised, exceptionally method is called:
[main] - Start CompletableFutureExample... [ForkJoinPool.commonPool-worker-3] - Inside supplyAsync [main] - exceptionally: java.lang.RuntimeException: Even time... [main] - ...End
As per our example, exceptionally will be invoked in the main thread.
CompletionStage::exceptionallyAsync
Two new methods to handle exception asynchronously:
- default CompletionStage<T> exceptionallyAsync(Function<Throwable,? extends T> fn): Returns a new CompletionStage that, when this stage completes exceptionally, is executed with this stage's exception as the argument to the supplied function, using this stage's default asynchronous execution facility.
- default CompletionStage<T> exceptionallyAsync(Function<Throwable,? extends T> fn, Executor executor): Returns a new CompletionStage that, when this stage completes exceptionally, is executed with this stage's exception as the argument to the supplied function, using the supplied Executor.
Let's check following example that using exceptionallyAsync without Executor:
import java.util.concurrent.CompletableFuture;
public class CompletableFutureExampleAsync {
public static void main(String[] args) throws Exception {
printWithThread("Start CompletableFutureExampleAsync...");
CompletableFuture.supplyAsync(() -> {
printWithThread("Inside supplyAsync");
if (System.currentTimeMillis() % 2 == 0) {
throw new RuntimeException("Even time..."); // 50% chance to fail
}
return "Winter is Coming!";
}).thenAcceptAsync(s -> {
printWithThread("thenAcceptAsync: " + s);
}).exceptionallyAsync(e -> {
printWithThread("exceptionallyAsync: " + e.getMessage());
return null;
});
Thread.sleep(500); // waiting for full response
printWithThread("...End");
}
private static void printWithThread(String desc) {
System.out.printf("[%s] - %s%n", Thread.currentThread().getName(), desc);
}
}
[main] - Start CompletableFutureExampleAsync... [ForkJoinPool.commonPool-worker-3] - Inside supplyAsync [ForkJoinPool.commonPool-worker-3] - exceptionallyAsync: java.lang.RuntimeException: Even time... [main] - ...End
We can see that the exceptionallyAsync is invoked in the same thread as supplyAsync. Let's check below example for exceptionallyAsync with Executor:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CompletableFutureExampleAsyncExecutor {
public static void main(String[] args) throws Exception {
printWithThread("Start CompletableFutureExampleAsyncExecutor...");
ExecutorService executor = Executors.newFixedThreadPool(10);
CompletableFuture.supplyAsync(() -> {
printWithThread("Inside supplyAsync");
if (System.currentTimeMillis() % 2 == 0) {
throw new RuntimeException("Even time..."); // 50% chance to fail
}
return "Winter is Coming!";
}).thenAcceptAsync(s -> {
printWithThread("Result: " + s);
}).exceptionallyAsync(e -> {
printWithThread("exceptionallyAsync: " + e.getMessage());
return null;
}, executor
).thenApply(s -> {
printWithThread("Inside thenApply");
return "The Winds of Winter!";
}).thenAccept(CompletableFutureExampleAsyncExecutor::printWithThread);
Thread.sleep(500); // waiting for full response
printWithThread("...End");
}
private static void printWithThread(String desc) {
System.out.printf("[%s] - %s%n", Thread.currentThread().getName(), desc);
}
}
[main] - Start CompletableFutureExampleAsyncExecutor... [ForkJoinPool.commonPool-worker-3] - Inside supplyAsync [pool-1-thread-1] - exceptionallyAsync: java.lang.RuntimeException: Even time... [pool-1-thread-1] - Inside thenApply [pool-1-thread-1] - The Winds of Winter! [main] - ...End
Per our example, with Executor the exceptionallyAsync will be invoked in the new thread.
exceptionallyCompose and exceptionallyComposeAsync
The next three methods that when this stage completes exceptionally, will returns a new CompletionStage that is composed using the results of the supplied function applied to this stage's exception.
- default CompletionStage<T> exceptionallyCompose(Function<Throwable,? extends CompletionStage<T>> fn): Returns a new CompletionStage that, when this stage completes exceptionally, is composed using the results of the supplied function applied to this stage's exception.
- default CompletionStage<T> exceptionallyComposeAsync(Function<Throwable,? extends CompletionStage<T>> fn): Returns a new CompletionStage that, when this stage completes exceptionally, is composed using the results of the supplied function applied to this stage's exception, using this stage's default asynchronous execution facility.
- default CompletionStage<T> exceptionallyComposeAsync(Function<Throwable,? extends CompletionStage<T>> fn,Executor executor): Returns a new CompletionStage that, when this stage completes exceptionally, is composed using the results of the supplied function applied to this stage's exception, using the supplied Executor.
This gives us more tools to recover from all the things that can break out there, as per next sample:
import java.util.concurrent.CompletableFuture;
public class CompletableFutureExampleComposeAsync {
public static void main(String[] args) throws Exception {
printWithThread("Start CompletableFutureExampleComposeAsync...");
CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
printWithThread("Inside CF1 supplyAsync");
if (System.currentTimeMillis() % 2 == 0) {
throw new RuntimeException("Even time..."); // 50% chance to fail
}
return "Winter is Coming!";
});
CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(() -> {
printWithThread("Inside CF2 supplyAsync");
return "The Winds of Winter!";
});
CompletableFuture<String> excCompose = cf1.exceptionallyComposeAsync(e -> {
printWithThread("exceptionally: " + e.getMessage());
return cf2;
});
excCompose.thenAcceptAsync(s -> {
printWithThread("thenAcceptAsync: " + s);
});
Thread.sleep(500); // waiting for full response
printWithThread("...End");
}
private static void printWithThread(String desc) {
System.out.printf("[%s] - %s%n", Thread.currentThread().getName(), desc);
}
}
If no RuntimeException, here the output:
[main] - Start CompletableFutureExampleComposeAsync... [ForkJoinPool.commonPool-worker-5] - Inside CF2 supplyAsync [ForkJoinPool.commonPool-worker-3] - Inside CF1 supplyAsync [ForkJoinPool.commonPool-worker-3] - thenAcceptAsync: Winter is Coming! [main] - ...End
When RuntimeException happen:
[main] - Start CompletableFutureExampleComposeAsync... [ForkJoinPool.commonPool-worker-3] - Inside CF1 supplyAsync [ForkJoinPool.commonPool-worker-5] - Inside CF2 supplyAsync [ForkJoinPool.commonPool-worker-5] - exceptionally: java.lang.RuntimeException: Even time... [ForkJoinPool.commonPool-worker-3] - thenAcceptAsync: The Winds of Winter! [main] - ...End