https://stackoverflow.com/questions/44409962/throwing-exception-from-completablefuture
来自stackoverflow的一篇文章,
14
6
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()
.
java exception exception-handling java-8 completable-future
8,07023467
asked Jun 7 '17 at 10:23
2,18832246
add a comment
4 Answers
24
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.
answered Jun 7 '17 at 11:31
169k23238453
-
1
this is so nice... – Eugene Jun 7 '17 at 12:04
-
very detailed answer. – holi-java Jun 7 '17 at 12:20
add a comment
4
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;
}
answered Jan 4 at 6:24
4,13433950
add a comment
3
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();
}
answered Jun 7 '17 at 10:28
70.9k9102169
-
I need to throw a
ServerException
only. – ayushgp Jun 7 '17 at 10:32 -
1
@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? – Eugene Jun 7 '17 at 10:42
-
2
It looks like your
go()
method would never throw anything. I guess it's missing ajoin()
call. Also,Wrapper
does not provide much more than what's already available withThrowable.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. – Didier L Jun 7 '17 at 15:16 -
2
@Eugene I meant that in the current form of
go()
, yourtry
/catch
would never actually catch theWrapper
. I find it quite misleading. ForWrapper
I meant you should callsuper(Throwable)
instead of defining your own field, so thatprintStackTrace()
andgetCause()
would behave as naturally expected from such a wrapper. – Didier L Jun 7 '17 at 15:36 -
1
@DidierL ah! got it, thank you for your comment – Eugene Jun 7 '17 at 15:58
1
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);
}
});
}};
2,5521325
answered Jun 7 '17 at 12:20
17k32847
-
4
There is no need to do that in an anonymous subclass at all. The subclass only wastes resources. See also here and here… – Holger Jun 7 '17 at 12:39
-
@Holger thank you, sir. I only write it up in my mind. and I'll see it later. – holi-java Jun 7 '17 at 13:10
-
@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. – holi-java Jun 7 '17 at 13:37