Question: how can I directly throw a custom exception from .exceptionally()
?
List<CompletableFuture<Object>> futures =
tasks.stream()
.map(task -> CompletableFuture.supplyAsync(() -> businessLogic(task))
.exceptionally(ex -> {
if (ex instanceof BusinessException) return null;
//TODO how to throw a custom exception here??
throw new BadRequestException("at least one async task had an exception");
}))
.collect(Collectors.toList());
try {
List<Object> results = futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList());
} catch (CompletionException e) {
if (e.getCause() instanceof RuntimeException) {
throw (RuntimeException) e.getCause();
}
throw new RuntimeException(e.getCause());
}
Problem: I just always get a CompletionException
whose ex.getCause()
is instanceof BadRequestException
.
Is that possible at all?
In this case no exception is thrown by Java unless we call get () or join () methods. On calling these methods CompletionException is thrown which wraps the actual exception as the root cause exception. Also we can use CompletableFuture.isCompletedExceptionally () method to determine if a CompletableFuture completed with an exception.
You can use the following methods to handle exceptions in Java 8+ CompletableFuture The stage created by exceptionally / exceptionallyAsync and whenComplete / whenCompleteAsync can only be executed when there're exceptions in the previous stages, while by handle / handleAsync, it can be executed in both normal and exception cases
Simply if there's no exception then exceptionally () stage is skipped otherwise it is executed. The exception passed to the exceptionally function is CompletionException which wraps the actual exception as its root cause. exceptionally () stage can be used to provide a replacement result.
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.
As said by Didier L, exceptions thrown by the functions (or generally exceptions that completed a CompletableFuture
) are always wrapped in a CompletionException
(unless they are already a CompletionException
or CancellationException
).
But note that your code becomes much simpler when not even trying to translate the exception via exceptionally
:
List<CompletableFuture<Object>> futures =
tasks.stream()
.map(task -> CompletableFuture.supplyAsync(() -> businessLogic(task)))
.collect(Collectors.toList());
try {
List<Object> results = futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList());
} catch (CompletionException e) {
throw e.getCause() instanceof BusinessException?
new BadRequestException("at least one async task had an exception"): e;
}
or
… catch (CompletionException e) {
throw e.getCause() instanceof BusinessException?
new BadRequestException("at least one async task had an exception"):
e.getCause() instanceof BusinessException? (RuntimeException)e.getCause(): e;
}
Since exceptionally
’s primary purpose is translating an exception to a non-exceptional result value, using it for translating the exception to another thrown exception was not the best fit anyway and it also needed an instanceof
. So performing this translation in the catch
clause saves you from another translation step.
This is not possible. The Javadoc of join()
clearly states:
Returns the result value when complete, or throws an (unchecked) exception if completed exceptionally. To better conform with the use of common functional forms, if a computation involved in the completion of this CompletableFuture threw an exception, this method throws an (unchecked)
CompletionException
with the underlying exception as its cause.
(emphasis is mine)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With