CompletableFuture.allOf() not completing after individual futures

Viewed 9165

When I use CompletableFuture.allOf() to combine independent completable futures as described in the javadoc it does not reliably complete after all futures supplied to the method. E.g.:

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class CompletableFutureTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Runnable dummyTask = () -> {
            try {
                Thread.sleep(200);
            } catch (InterruptedException ignored) {
            }
        };

        CompletableFuture<Void> f1 = CompletableFuture.runAsync(dummyTask);
        CompletableFuture<Void> f2 = CompletableFuture.runAsync(dummyTask);
        CompletableFuture[] all = {f1, f2};
        f1.whenComplete((aVoid, throwable) -> System.out.println("Completed f1"));
        f2.whenComplete((aVoid, throwable) -> System.out.println("Completed f2"));
        CompletableFuture<Void> allOf = CompletableFuture.allOf(all);
        allOf.whenComplete((aVoid, throwable) -> {
                    System.out.println("Completed allOf");
                }
        );
        allOf.join();
        System.out.println("Joined");
    }
}

Leads to the following result:

Completed f2
Joined
Completed allOf
Completed f1

I would expect the logging "Joined" and "Completed allOf" to be written after "Completed f1" and "Completed f2". To make things more confusing the order of the futures in the array seems top matter. If I changed the line

CompletableFuture[] all = {f1, f2};

to

CompletableFuture[] all = {f2, f1};

The resulting output changes to:

Completed allOf
Completed f1
Completed f2
Joined

And to make things even worse, if I run the exact same code several times, the order changes again. I could understand that the order of "f1" and "f2" change randomly, and likewise for "allOf" and "Joined". But this is really surprising.

In case it matters: This is JDK 1.8.0_91 on Windows 7.

3 Answers

f1.whenComplete returns a new future that is independent from f1. AllOf will wait for f1 to complete, but not wait for the lambda passed to whenComplete to complete. To get the result you're after, you can try something like:

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class CompletableFutureTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Runnable dummyTask = () -> {
            try {
                Thread.sleep(200);
            } catch (InterruptedException ignored) {
            }
        };

        CompletableFuture<Void> f1 = CompletableFuture.runAsync(dummyTask);
        CompletableFuture<Void> f2 = CompletableFuture.runAsync(dummyTask);
        f1 = f1.whenComplete((aVoid, throwable) -> System.out.println("Completed f1"));
        f2 = f2.whenComplete((aVoid, throwable) -> System.out.println("Completed f2"));
        CompletableFuture[] all = {f1, f2};
        CompletableFuture<Void> allOf = CompletableFuture.allOf(all);
        allOf.whenComplete((aVoid, throwable) -> {
            System.out.println("Completed allOf");
        });
        allOf.join();
        System.out.println("Joined");
    }
}

This is OK - No one guaranteed the order of callbacks.

Call join on the wenComplete method

allOf.whenComplete((aVoid, throwable) -> {
                System.out.println("Completed allOf");
            }
    ).join();
Related