The Javadoc for CompletableFuture says, "...this method is not designed to translate completion outcomes, so the supplied action
should not throw an exception." But then it tells you what to expect if the action
does throw an exception...
it seems as though an exception thrown from within the whenComplete bubbled up as if the exception were thrown in a regular chain.
...The Javadoc says, "if this stage completed normally but the supplied action throws an exception, then the returned stage completes exceptionally with the supplied action's exception."
That sounds like the behavior you are seeing.
Response to comment:
But if task B throwing an exception causes task A (upon completing normally) to throw task B's exception, then doesn't that mean task A has to wait on task B?
No. There's no waiting. A CompletableFuture
can be completed in either of two ways: It can be completed normally (e.g., as if by complete(T value)
), or it can be completed exceptionally (as if by completeExceptionally(Throwable ex)
). In the "exceptionally" case, a reference to the Throwable ex
object will be stored in the future object.
Some time later, when some other thread eventually calls future.get()
it will return the stored value
if the the future was normally completed, but if the future was exceptionally completed, then the get() call will throw new ExecutionException(ex)
where ex
was the original Throwable
object that was stored in the future.
So really, there's two exceptions thrown; One to report whatever the original bad thing was that happened when it happened, and then another that is deferred until some other thread calls future.get()
. In your example, the "bad thing" happened when some pool thread performed the action
that you supplied to whenComplete(...,action)
.