Is it possible for these methods to receive an exception other than
CompletionException
?
Yes, it is possible and you shouldn't cast to CompletionException
without an instanceof
check (or a review of your usage).
Take this example
CompletableFuture<Void> root = new CompletableFuture<>();
root.whenComplete((v, t) -> {
System.out.println(t.getClass()); // class java.io.IOException
});
root.completeExceptionally(new IOException("blow it up"));
whenComplete
will receive the IOException
rather than a CompletionException
wrapping it. The same behavior applies to exceptionally
and handle
.
A stage's computation is defined in the Javadoc:
The computation performed by a stage may be expressed as a Function
,
Consumer
, or Runnable
(using methods with names including apply,
accept, or run, respectively) depending on whether it requires
arguments and/or produces results.
I believe this quote
if a stage's computation terminates abruptly with an (unchecked)
exception or error
is referring to one of those Function#apply
, Consumer#accept
, or Runnable#run
methods terminating abruptly because of a thrown exception, not because a stage completed exceptionally through some other mechanism.
Note also that the Javadoc says
This interface does not define methods for initially creating,
forcibly completing normally or exceptionally, probing completion
status or results, or awaiting completion of a stage. Implementations
of CompletionStage
may provide means of achieving such effects, as
appropriate
In other words, the interface allows implementations to complete stages exceptionally without abruptly terminating any computation. I think this allows for new behavior.
If we extend my example from before
CompletableFuture<Void> root = new CompletableFuture<>();
CompletableFuture<Void> child = root.whenComplete((v, t) -> {
System.out.println(t.getClass()); // class java.io.Exception
});
child.whenComplete((v, t) -> {
System.out.println(t.getClass()); // class java.util.concurrent.CompletionException
});
root.completeExceptionally(new IOException("blow it up"));
You'll notice the completion attached to the child
receives a CompletionException
wrapping the original IOException
. This isn't obvious to me from the Javadoc, which states
Returns a new CompletionStage
with the same result or exception as
this stage
All in all, it seems like the raw exception from a completeExceptionally
is passed down to direct dependents, while dependents of dependents receive an enclosing CompletionException
.