membersound
membersound

Reputation: 86637

How to throw a custom exception from CompletableFuture?

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?

Upvotes: 8

Views: 17230

Answers (2)

Holger
Holger

Reputation: 298103

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 RuntimeException rte? rte: 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.

Upvotes: 13

Didier L
Didier L

Reputation: 20560

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)

Upvotes: 5

Related Questions