简体   繁体   中英

CompletableFuture#exceptionally rethrow checked exception

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.

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)
            });
}

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. Eg 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);
    });

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM