Search code examples
javafuturecompletable-future

CompletableFuture doesn't work if it saved to variable


Why does it not work when I save the future to variable before configuration steps?

@Test
void simple() {
    CompletableFuture<String> future = CompletableFuture.supplyAsync(this::throwException)
            .exceptionally(throwable -> HANDLED);
    assertEquals(HANDLED, future.join());
}

@Test
void withVar() {
    CompletableFuture<String> future = CompletableFuture.supplyAsync(this::throwException);
    future.exceptionally(throwable -> HANDLED);
    assertEquals(HANDLED, future.join());
}

private String throwException() {
    if (true) {
        throw new RuntimeException(FAIL);
    }
    return SUCCESS;
}

simple() is fine, but withVar() doesn't work:

java.util.concurrent.CompletionException: java.lang.RuntimeException: FAIL

at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:314)
at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:319)
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1702)
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1692)
at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183)`
Caused by: java.lang.RuntimeException: FAIL
    at ru.dionisis.resttest.ComplitableFutureTest.throwException(ComplitableFutureTest.java:35)
    at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1700)
    ... 6 more`

A similar problem was with RestTemplateBuilder, when I wanted make otional init one field


Solution

  • From the JavaDoc for exceptionally: "Returns a new CompletableFuture...". It does not mutate the CompleteableFuture that it's called upon.

    You are creating a new CompleteableFuture with new behaviour and then throwing that away, then joining on the original CompleteableFuture which does not have that behaviour.

    It's like saying that String::toUpperCase doesn't work in this example. It does work, but the result is thrown away.

    String foo = "bar";
    foo.toUpperCase();
    assertEquals("BAR", foo);