Reputation: 3077
Suppose I want to recover with some value if I get a specific exception, otherwise return the failed future with the exception. I would expect something like this:
public static void main(String[] args) {
CompletableFuture
.supplyAsync(FuturesExample::fetchValue)
.exceptionally(throwable -> {
if (throwable instanceof RuntimeException) {
return "All good";
}
throw throwable; // does not compile
});
}
public static String fetchValue() {
// code that potentially throws an exception
return "value";
}
If the fetchValue
function would throw a checked exception, I would like to handle it in the chained methods. I have tried both return throwable
and throw throwable
, but neither does compile. Do CompletableFuture
s offer any solution to this scenario? I am aware that the Function
interface that is the parameter of the exceptionally
method does not throw any exceptions - I would just like to return the already failed future in this case. I would like to find a solution using Java 8.
Upvotes: 11
Views: 13530
Reputation: 7855
As holger wrote, this is normally not possible.
But, there is a trick with lombok and it's @SneakyThrows.
public static void main(String[] args) {
CompletableFuture
.supplyAsync(FuturesExample::fetchValue)
.exceptionally(throwable -> {
if (throwable instanceof RuntimeException) {
return "All good";
}
FutureExample.reThrow(throwable);
// maybe a "return null" is necessary here (even when it is not reachable)
});
}
public static String fetchValue() {
// code that potentially throws an exception
return "value";
}
@SneakyThrows // <- threat checked exceptions in method-body as unchecked
public static void reThrow(Throwable throwable) {
throw throwable;
}
You can also archive it with ExceptionUtils.rethrow()
(thanks vlp).
public static void main(String[] args) {
CompletableFuture
.supplyAsync(FuturesExample::fetchValue)
.exceptionally(throwable -> {
if (throwable instanceof RuntimeException) {
return "All good";
}
reThrow(throwable);
// maybe a "return null" is necessary here (even when it is not reachable)
});
}
Upvotes: 3
Reputation: 298233
In this scenario, it is impossible to receive a checked exception as the previous stage is based on a Supplier
, which is not allowed to throw checked exceptions.
So you could handle all unchecked exceptions and raise an AssertionError
for the throwables that ought to be impossible:
CompletableFuture
.supplyAsync(FuturesExample::fetchValue)
.exceptionally(throwable -> {
if (throwable instanceof RuntimeException) {
return "All good";
}
if(throwable instanceof Error) throw (Error)throwable;
throw new AssertionError(throwable);
});
Otherwise, you may consider that subsequent stages, as well as callers of join()
will get all exceptions except CompletionException
and CancellationException
wrapped in a CompletionException
anyway. E.g. when I use
public static void main(String[] args) {
CompletableFuture<String> f = CompletableFuture
.supplyAsync(FuturesExample::fetchValue)
.exceptionally(throwable -> {
if(throwable instanceof RuntimeException) {
throw (RuntimeException)throwable;
}
throw new Error();
});
f.whenComplete((s,t) -> {
if(t != null) {
System.err.println("in whenComplete handler ");
t.printStackTrace();
}
});
System.err.println("calling join()");
f.join();
}
public static String fetchValue() {
throw new IllegalStateException("a test is going on");
}
I get
in whenComplete handler
java.util.concurrent.CompletionException: java.lang.IllegalStateException: a test is going on
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.IllegalStateException: a test is going on
at FuturesExample.fetchValue(FuturesExample.java:23)
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1700)
... 6 more
calling join()
Exception in thread "main" java.util.concurrent.CompletionException: java.lang.IllegalStateException: a test is going on
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.IllegalStateException: a test is going on
at FuturesExample.fetchValue(FuturesExample.java:23)
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1700)
... 6 more
So I can use CompletionException
for wrapping arbitrary throwables, utilizing the fact that CompletionException
does not get wrapped again. So if I use
public static void main(String[] args) {
CompletableFuture<String> f = CompletableFuture
.supplyAsync(FuturesExample::fetchValue)
.exceptionally(throwable -> {
if(throwable instanceof CompletionException)
throwable = throwable.getCause();
System.err.println("wrapping '"+throwable+"' inside exceptionally");
throw new CompletionException(throwable);
});
f.whenComplete((s,t) -> {
if(t != null) {
System.err.println("in whenComplete handler ");
t.printStackTrace();
}
});
System.err.println("calling join()");
f.join();
}
public static String fetchValue() {
throw new IllegalStateException("a test is going on");
}
I get
wrapping 'java.lang.IllegalStateException: a test is going on' inside exceptionally
in whenComplete handler
java.util.concurrent.CompletionException: java.lang.IllegalStateException: a test is going on
at FuturesExample.lambda$main$0(FuturesExample.java:12)
at java.base/java.util.concurrent.CompletableFuture.uniExceptionally(CompletableFuture.java:986)
at java.base/java.util.concurrent.CompletableFuture$UniExceptionally.tryFire(CompletableFuture.java:970)
at java.base/java.util.concurrent.CompletableFuture.unipush(CompletableFuture.java:589)
at java.base/java.util.concurrent.CompletableFuture.uniExceptionallyStage(CompletableFuture.java:1002)
at java.base/java.util.concurrent.CompletableFuture.exceptionally(CompletableFuture.java:2307)
at FuturesExample.main(FuturesExample.java:8)
Caused by: java.lang.IllegalStateException: a test is going on
at FuturesExample.fetchValue(FuturesExample.java:24)
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1700)
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)
calling join()
Exception in thread "main" java.util.concurrent.CompletionException: java.lang.IllegalStateException: a test is going on
at FuturesExample.lambda$main$0(FuturesExample.java:12)
at java.base/java.util.concurrent.CompletableFuture.uniExceptionally(CompletableFuture.java:986)
at java.base/java.util.concurrent.CompletableFuture$UniExceptionally.tryFire(CompletableFuture.java:970)
at java.base/java.util.concurrent.CompletableFuture.unipush(CompletableFuture.java:589)
at java.base/java.util.concurrent.CompletableFuture.uniExceptionallyStage(CompletableFuture.java:1002)
at java.base/java.util.concurrent.CompletableFuture.exceptionally(CompletableFuture.java:2307)
at FuturesExample.main(FuturesExample.java:8)
Caused by: java.lang.IllegalStateException: a test is going on
at FuturesExample.fetchValue(FuturesExample.java:24)
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1700)
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)
which differs slightly in the stack traces, but does not make a difference to the code receiving/catching the exception, as in either case, it’s a CompletionException
wrapping an IllegalStateException
.
So going back to the example of your question, you could use
CompletableFuture
.supplyAsync(FuturesExample::fetchValue)
.exceptionally(throwable -> {
if (throwable instanceof RuntimeException) { // includes CompletionException
return "All good";
}
throw new CompletionException(throwable);
});
Since CompletionException
is a RuntimeException
, this code handles it and avoids wrapping a CompletionException
in another CompletionException
. Otherwise, the pattern would be
.exceptionally(throwable -> {
if (some condition) {
return some value;
}
throw throwable instanceof CompletionException?
(CompletionException)throwable: new CompletionException(throwable);
});
Upvotes: 11