I have encountered strange behavior of Java 8 CompletableFuture thenCompose method. I have two tests that differ only in order of execution. Both tests simulate failure in the CompletableFuture generated in thenCompose.
@Test
public void completedAfter() {
CompletableFuture<String> future1 = new CompletableFuture<>();
CompletableFuture<String> future2 = new CompletableFuture<>();
future1.thenCompose(x -> future2).whenComplete((r, e) -> System.out.println("After: " + e));
future1.complete("value");
future2.completeExceptionally(new RuntimeException());
}
@Test
public void completedBefore() {
CompletableFuture<String> future1 = new CompletableFuture<>();
CompletableFuture<String> future2 = new CompletableFuture<>();
future1.complete("value");
future2.completeExceptionally(new RuntimeException());
future1.thenCompose(x -> future2).whenComplete((r, e) -> System.out.println("Before: " +e));
}
The output is:
After: java.util.concurrent.CompletionException: java.lang.RuntimeException
Before: java.lang.RuntimeException
The question is, why is the exception wrapped in CompletionException
in one case but not in the other?
Update: Here is related bug report. It has been marked and resolved as a bug in JDK.
Seems like a bug in the jdk library.
In the "After" case, .thenCompose
adds a ThenCopy
completion node to the target future, whose execution is later triggered by .completeExceptionally
. The completion node's run
method finds the exception on the future, and calls .internalComplete
on the destination, that wraps all exceptions into CompletionException
.
See here how the node is created, and here for where the wrapping happens.
Now, in the Before
case, the code path is completely different. Because the future is already completed, .thenCompose
does not create additional nodes, but invokes the callback right away, and simply returns an (already completed second future), on which you then call .whenComplete
, which, again, does not bother to create a new completion node, but simply invokes the callback right away, giving it the original exception from the second future.
Boy, looking at this code, there are so many examples I want to show to my students of what they should never do ...
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