While answering this question, I noticed a strange behaviour of CompletableFuture
: if you have a CompletableFuture cf
and chain a call with cf.exceptionally()
, calling cf.get()
appears to behave strangely:
- if you call it before exceptional completion, it waits for the execution of the
exceptionally()
block before returning - otherwise, it fails immediately by throwing the expected
ExecutionException
Am I missing something or is this a bug? I am using Oracle JDK 1.8.0_131 on Ubuntu 17.04.
The following code illustrates this phenomenon:
public static void main(String[] args) {
long start = System.currentTimeMillis();
final CompletableFuture<Object> future = CompletableFuture.supplyAsync(() -> {
sleep(1000);
throw new RuntimeException("First");
}).thenApply(Function.identity());
future.exceptionally(e -> {
sleep(1000);
logDuration(start, "Exceptionally");
return null;
});
final CompletableFuture<Void> futureA = CompletableFuture.runAsync(() -> {
try {
future.get();
} catch (Exception e) {
} finally {
logDuration(start, "A");
}
});
final CompletableFuture<Void> futureB = CompletableFuture.runAsync(() -> {
sleep(1100);
try {
future.get();
} catch (Exception e) {
} finally {
logDuration(start, "B");
}
});
try {
future.join();
} catch (Exception e) {
logDuration(start, "Main");
}
futureA.join();
futureB.join();
}
private static void sleep(final int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private static void logDuration(long start, String who) {
System.out.println(who + " waited for " + (System.currentTimeMillis() - start) + "ms");
}
Output:
B waited for 1347ms
Exceptionally waited for 2230ms
Main waited for 2230ms
A waited for 2230ms
As you can see, futureB
which sleeps a bit before calling get()
does not block at all. However, both futureA
and the main thread wait for exceptionally()
to complete.
Note that this behaviour does not occur if you remove the .thenApply(Function.identity())
.
CompletableFuture
does not wait for exceptional to complete when callingget
after a sleep over the time the exception is thrown. Accroding to JavaDoc it looks likeget
wont necessarily have to wait: "Waits if necessary for this future to complete, and then returns its result." – Berfieldget
and since it does not have result yet it waits untill it gets exception and waits again untill it finishes calling exceptionally function before throwing exception back to A. Thread B calls get, since at this time we should already have exception result theget
will just throw it without again calling exception handler function. – Berfield