Search code examples
javajava-8concurrencyjava.util.concurrentcompletable-future

Why does calling CompletableFuture::cancel cause an immediate CancellationException


I am trying to call cancel on CompletableFuture.

It seems from the docs:

If not already completed, completes this CompletableFuture with a CancellationException. Dependent CompletableFutures that have not already completed will also complete exceptionally, with a CompletionException caused by this CancellationException.

That it should complete them exceptionally which is what I was expecting but instead, it throws and immediate CancellationException.

Here is a sample code

CompletableFuture<?> f = CompletableFuture.supplyAsync(() -> false);
f.cancel(true);  // Line 7.
f.join();

With a repro : https://www.mycompiler.io/view/2v1ME4u

Exception in thread "main" java.util.concurrent.CancellationException
    at java.base/java.util.concurrent.CompletableFuture.cancel(CompletableFuture.java:2396)
    at Main.main(Main.java:7)

Line 7 is the f.cancel(true); line.


Solution

  • It doesn't actually throw immediately.

    Calling f.cancel(true) causes a CancellationException to be created, capturing the stack trace of the call to cancel. So the stack trace (which is printed because it's unhandled) contains the line of the f.cancel(true); call.

    But that exception isn't actually thrown until f.join():

    Returns the result value when complete, or throws an (unchecked) exception if completed exceptionally

    ...

    Throws:

    CancellationException - if the computation was cancelled

    You can see this by putting in a few more print statements into your example code:

    CompletableFuture<?> f = CompletableFuture.supplyAsync(() -> false);
    f.cancel(true);  // Line 8.
    try {
        f.join();
    } catch (CancellationException e) {
        System.out.println("CancellationException was thrown at call to f.join()");
        e.printStackTrace(System.out);
    }
    

    Output:

    CancellationException was thrown at call to f.join()
    java.util.concurrent.CancellationException
        at java.base/java.util.concurrent.CompletableFuture.cancel(CompletableFuture.java:2396)
        at Main.main(Main.java:8)