Throwing exception from CompletableFuture

102,566

Solution 1

Your code suggests that you are using the result of the asynchronous operation later in the same method, so you’ll have to deal with CompletionException anyway, so one way to deal with it, is

public void myFunc() throws ServerException {
    // Some code
    CompletableFuture<A> a = CompletableFuture.supplyAsync(() -> {
        try { return someObj.someFunc(); }
        catch(ServerException ex) { throw new CompletionException(ex); }
    });
    // Some code running in parallel to someFunc()
    A resultOfA;
    try {
        resultOfA = a.join();
    }
    catch(CompletionException ex) {
        try {
            throw ex.getCause();
        }
        catch(Error|RuntimeException|ServerException possible) {
            throw possible;
        }
        catch(Throwable impossible) {
            throw new AssertionError(impossible);
        }
    }
    // some code using resultOfA
}

All exceptions thrown inside the asynchronous processing of the Supplier will get wrapped into a CompletionException when calling join, except the ServerException we have already wrapped in a CompletionException.

When we re-throw the cause of the CompletionException, we may face unchecked exceptions, i.e. subclasses of Error or RuntimeException, or our custom checked exception ServerException. The code above handles all of them with a multi-catch which will re-throw them. Since the declared return type of getCause() is Throwable, the compiler requires us to handle that type despite we already handled all possible types. The straight-forward solution is to throw this actually impossible throwable wrapped in an AssertionError.

Alternatively, we could use an alternative result future for our custom exception:

public void myFunc() throws ServerException {
    // Some code
    CompletableFuture<ServerException> exception = new CompletableFuture<>();
    CompletableFuture<A> a = CompletableFuture.supplyAsync(() -> {
        try { return someObj.someFunc(); }
        catch(ServerException ex) {
            exception.complete(ex);
            throw new CompletionException(ex);
        }
    });
    // Some code running in parallel to someFunc()
    A resultOfA;
    try {
        resultOfA = a.join();
    }
    catch(CompletionException ex) {
        if(exception.isDone()) throw exception.join();
        throw ex;
    }
    // some code using resultOfA
}

This solution will re-throw all “unexpected” throwables in their wrapped form, but only throw the custom ServerException in its original form passed via the exception future. Note that we have to ensure that a has been completed (like calling join() first), before we query the exception future, to avoid race conditions.

Solution 2

For those looking for other ways on exception handling with completableFuture

Below are several ways for example handling Parsing Error to Integer:

1. Using handle method - which enables you to provide a default value on exception

CompletableFuture correctHandler = CompletableFuture.supplyAsync(() -> "A")
            .thenApply(Integer::parseInt)
            .handle((result, ex) -> {
                if (null != ex) {
                    ex.printStackTrace();
                    return 0;
                } else {
                    System.out.println("HANDLING " + result);
                    return result;
                }
            })
            .thenAcceptAsync(s -> {
                System.out.println("CORRECT: " + s);
            });

2. Using exceptionally Method - similar to handle but less verbose

CompletableFuture parser = CompletableFuture.supplyAsync(() -> "1")
                .thenApply(Integer::parseInt)
                .exceptionally(t -> {
                    t.printStackTrace();
                    return 0;
                }).thenAcceptAsync(s -> System.out.println("CORRECT value: " + s));

3. Using whenComplete Method - using this will stop the method on its tracks and not execute the next thenAcceptAsync

CompletableFuture correctHandler2 = CompletableFuture.supplyAsync(() -> "A")
                .thenApply(Integer::parseInt)
                .whenComplete((result, ex) -> {
                    if (null != ex) {
                        ex.printStackTrace();
                    }
                })
                .thenAcceptAsync(s -> {
                    System.out.println("When Complete: " + s);
                });

4. Propagating the exception via completeExceptionally

public static CompletableFuture<Integer> converter(String convertMe) {
        CompletableFuture<Integer> future = new CompletableFuture<>();
        try {
            future.complete(Integer.parseInt(convertMe));
        } catch (Exception ex) {
            future.completeExceptionally(ex);
        }
        return future;
    }

Solution 3

Even if other's answer is very nice. but I give you another way to throw a checked exception in CompletableFuture.

IF you don't want to invoke a CompletableFuture in another thread, you can use an anonymous class to handle it like this:

CompletableFuture<A> a = new CompletableFuture<A>() {{
    try {
        complete(someObj.someFunc());
    } catch (ServerException ex) {
        completeExceptionally(ex);
    }
}};

IF you want to invoke a CompletableFuture in another thread, you also can use an anonymous class to handle it, but run method by runAsync:

CompletableFuture<A> a = new CompletableFuture<A>() {{
    CompletableFuture.runAsync(() -> {
        try {
            complete(someObj.someFunc());
        } catch (ServerException ex) {
            completeExceptionally(ex);
        }
    });
}};

Solution 4

I think that you should wrap that into a RuntimeException and throw that:

 throw new RuntimeException(ex);

Or many be a small utility would help:

static class Wrapper extends RuntimeException {
    private Wrapper(Throwable throwable) {
        super(throwable);
    }
    public static Wrapper wrap(Throwable throwable) {
        return new Wrapper(throwable);
    }
    public Throwable unwrap() {
        return getCause();
    }
}
 public static void go() {
    CompletableFuture<String> a = CompletableFuture.supplyAsync(() -> {
        try {
            throw new Exception("Just because");
        } catch (Exception ex) {
            throw Wrapper.wrap(ex);
        }
    });
    a.join();
}

And then you could unwrap that..

 try {
        go();
 } catch (Wrapper w) {
        throw w.unwrap();
 }
Share:
102,566
ayushgp
Author by

ayushgp

I blog at https://ayushgp.github.io.

Updated on July 05, 2022

Comments

  • ayushgp
    ayushgp 11 months

    I have the following code:

    // How to throw the ServerException?
    public void myFunc() throws ServerException{
        // Some code
        CompletableFuture<A> a = CompletableFuture.supplyAsync(() -> {
            try {
                return someObj.someFunc();
            } catch(ServerException ex) {
                // throw ex; gives an error here.
            }
        }));
        // Some code
    }
    

    someFunc() throws a ServerException. I don't want to handle this here but throw the exception from someFunc() to caller of myFunc().

  • ayushgp
    ayushgp almost 6 years
    I need to throw a ServerException only.
  • Eugene
    Eugene almost 6 years
    @ayushgp i don't see this happening with default streams, since they do not allow checked exceptions... may be you would be ok with wrapping that one and than unwrapping?
  • Holger
    Holger almost 6 years
    There is no need to do that in an anonymous subclass at all. The subclass only wastes resources. See also here and here
  • holi-java
    holi-java almost 6 years
    @Holger thank you, sir. I only write it up in my mind. and I'll see it later.
  • holi-java
    holi-java almost 6 years
    @Holger sir, I found your two answers are different. and I prefer your first one that you used in this question. because it is easy to use and very clearly.
  • Didier L
    Didier L almost 6 years
    It looks like your go() method would never throw anything. I guess it's missing a join() call. Also, Wrapper does not provide much more than what's already available with Throwable.getCause(). I wouldn't wrap an exception into another one without setting the cause, as it breaks the convention and it will not print proper stacktraces.
  • Eugene
    Eugene almost 6 years
    @DidierL yes go was there just to prove a point, it's not very useful indeed. Wrapper on the other hand is here just to wrap the checked exception into a runtime one.
  • Didier L
    Didier L almost 6 years
    @Eugene I meant that in the current form of go(), your try/catch would never actually catch the Wrapper. I find it quite misleading. For Wrapper I meant you should call super(Throwable) instead of defining your own field, so that printStackTrace() and getCause() would behave as naturally expected from such a wrapper.
  • John Red
    John Red almost 4 years
    For those of you, like me, who are unable to use 1, 2 and 3 because of CompletableFuture<Void>, just return a null where an object of type Void is expected :) Took me hours to figure out this simple thing.
  • Dmitry K
    Dmitry K almost 4 years
    Guava has helper methods. Catch looks like this: Throwables.throwIfUnchecked(e.getCause()); throw new RuntimeException(e.getCause());
  • Gaurav
    Gaurav over 3 years
    @Holger excellent answer! one needs to block on join to catch and throw exceptions in async
  • Mr Matrix
    Mr Matrix over 3 years
    @Holger: Why not use get() method? Wouldn't that simply the multi-catch block?
  • Holger
    Holger over 3 years
    @Miguel get differs from join by wrapping exceptions in an ExecutionException instead of CompletionException. This provides no improvement to the catch side. It also requires the caller to handle InterruptedException, which makes it more complicated. Further, since a Supplier can’t throw a checked ExecutionException, it has to stay with CompletionException, then get will extract the cause and re-wrap it in an ExecutionException, which makes it less efficient. Not that performance matters much for the exceptional case. But get is worse than join in every aspect here.
  • RAGINROSE
    RAGINROSE about 1 year
    how to test this code? mainly than catch part (CompletionException ex) ?