27

The CompletionStage Javadoc states:

[...] if a stage's computation terminates abruptly with an (unchecked) exception or error, then all dependent stages requiring its completion complete exceptionally as well, with a CompletionException holding the exception as its cause.

Seeing as exceptional completions always wrap exceptions in CompletionException why do exceptionally(), whenComplete() and handle() represent the exception as Throwable instead of CompletionException?

This matters because it prevents one from directly re-throwing exceptions inside these methods.

Is it possible for these methods to receive an exception other than CompletionException? Or can I safely force a cast to this type?

(I ran some tests locally, as well as dig through the CompletableFuture source-code and, at first glance, I do not see how any other type of exception could be thrown.)

Hearen
  • 7,420
  • 4
  • 53
  • 63
Gili
  • 86,244
  • 97
  • 390
  • 689
  • 2
    Interesting question. I would write up a quick [mcve] to see what it actually does... but it's WAY past my bedtime :-) – Jim Garrison Mar 12 '18 at 08:52
  • @JimGarrison I ran some tests locally, as well as dig through the `CompletableFuture` code and at first glance I do not see how any other type of exception could be thrown. I will update the question accordingly. – Gili Mar 12 '18 at 08:54
  • good question :) never thought about it this way – Eugene Mar 12 '18 at 09:21
  • Register a `whenComplete` with a raw `CompletableFuture`, then `completeExceptionally` passing a checked exception. – Sotirios Delimanolis Mar 13 '18 at 02:49
  • @SotiriosDelimanolis I'm not sure I understand what you mean. Can you please post an answer with a code sample? – Gili Mar 13 '18 at 02:51
  • [Here's](https://ideone.com/AAsuf9) what I mean. I tried to write an answer but there's always something in the documentation that blocks a full explanation. The gist of it was that the quote you posted refers to the `Function`, `Consumer`, or `Runnable` used in a continuation throwing an exception, not to an exception used to complete a `CompletionStage`. Note that the interface _does not define methods for initially creating, forcibly completing normally or exceptionally [...]_. Those use cases are deferred to implementations. – Sotirios Delimanolis Mar 13 '18 at 03:23
  • @SotiriosDelimanolis You are right. Now that I know what to look for in the source-code, I see two methods that can throw arbitrary exceptions: `completeExceptionally()` and `obtrudeException()`. Post an answer with these details and I'll mark it as accepted. – Gili Mar 13 '18 at 03:52

1 Answers1

17

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.

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
  • According to discussion on Java bug JDK-8233050, this is as (confusingly) documented - the second `whenComplete()` treats the previous step's propagation of an exception as the abrupt termination of a computation stage. Apparently I'm not the only one to disagree with this as there also exist bugs JDK-8172177, JDK-8199501 and JDK-8068432 (possibly more, but you get the gist), all unresolved and unattended. – Guss Oct 18 '21 at 10:56