Behavior of CompletableFuture allOf method

CompletableFuture allOf

1. Overview

In this article, we will discuss the behavior of the CompletableFuture allOf method. The CompletableFuture interface was added to the java.util.concurrent package in the Java 8 release.

The purpose of CompletableFuture interface is asynchronous programming. In other words, it runs the code as a task in a non-blocking thread. After execution, it notifies the caller thread about the task progress, completion, or any failure. To learn more about CompletableFuture, refer to our articles.

2. CompletableFuture allOf method

The allOf method of the CompletableFuture accepts an array of CompletableFutures as an argument and returns a new CompletableFuture. Using the new CompletableFuture, you can wait for those provided CompletableFutures to complete normally or exceptionally.

You had to wait for this returned CompletableFuture to complete by using any of the callback methods such as get(), join(), whenComplete() so on.

If any of the provided CompletableFutures complete exceptionally, then the returned CompletableFuture also throws a CompletionException having the aforesaid exception as its cause. Note that the returned CompletableFuture does not reflect the results of the provided CompletableFutures. However, you can get the result by inspecting each CompletableFuture individually.

Assume our project registers a new user after validating their details. So we have two CompletableFuture instances: one for validating the details and another for registering the new user. If the validation and registration are successful, then we want to update UI success or failure if any exceptions.

So we had to create a new CompletableFuture using allOf method which can wait for these two CompletableFuture instances to complete and return an exception if any.

Let’s see a few examples to understand the allOf method.

2.1. allOf with no exceptions

The below CompletableFuture validates the new user.

// first CompletableFuture
CompletableFuture<Boolean> validateNewUser = CompletableFuture.supplyAsync(() -> {
     try {
          // validate new user details
          // inducing sleep for demo
          Thread.sleep(1000);
     } catch (InterruptedException e) {
          e.printStackTrace();
     }
     System.out.println("Validation of new user : SUCCESS");
     return true;
});

The below CompletableFuture handles the registration of the new user.

// second CompletableFuture
CompletableFuture<Boolean> registerNewUser = CompletableFuture.supplyAsync(() -> {
    try {
         // perform registration
         // inducing sleep for demo purpose
         Thread.sleep(1000);
    } catch (InterruptedException e) {
         e.printStackTrace();
    }
    System.out.println("Registration of new user : success");
    return true;
});

In the below code, the allOf method waits for both validateNewUser and registerNewUser to complete and returns a new CompletableFuture finalCompletableFuture. Note that you have to wait for the returned finalCompletableFuture to complete. Here, we are using the whenComplete to wait asynchronously.

Once all the provided CompletableFuture completes, then the whenComplete method of the finalCompletableFuture executes. You can check throwable for any exception.

CompletableFuture<Void> finalCompletableFuture = CompletableFuture.allOf(validateNewUser,
                registerNewUser);
finalCompletableFuture.whenComplete((result, throwable) -> {
    System.out.println("Result : " + result);
    System.out.println("Throwable : " + throwable);
    if (throwable == null) {
        // update UI for success
    } else {
        // update UI failure
    }
});

If you execute the above code, then it prints the following:

Validation of new user : SUCCESS
Registration of new user : success
Result : null
Throwable : null

Note that the result is null, because the CompletableFuture returned by allOf is always of void type and doesn’t return the result of the provided CompletableFuture instances.

2.2. allOf example with exception

If there are any exceptions in the provided CompletableFuture, then the CompletableFuture returned by allOf throws CompletionException.

// first CompletableFuture
CompletableFuture<Boolean> validateNewUser = CompletableFuture.supplyAsync(() -> {
    try {
        // validate new user details
        // inducing sleep for demo
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    boolean validationFailed = true;
    if (validationFailed) {
       throw new NullPointerException();
    }
    return true;
});
CompletableFuture<Void> finalCompletableFuture = CompletableFuture.allOf(validateNewUser,
                registerNewUser);
finalCompletableFuture.whenComplete((result, throwable) -> {
    System.out.println("Result : " + result);
    System.out.println("Throwable : " + throwable);
    if (throwable == null) {
        // update UI for success
    } else {
        // update UI failure
    }
});

Now, if you execute the code again, then the throwable of the whenComplete captures the exception and prints.

Result : null
Throwable : java.util.concurrent.CompletionException: java.lang.NullPointerException

The finalCompletableFuture also throws the same exception having it cause within the CompletationException:

For example, we are using the join() method to wait synchronously for the finalCompletableFuture.

CompletableFuture<Void> finalCompletableFuture = CompletableFuture.allOf(validateNewUser,
                registerNewUser);
try {
     System.out.println(finalCompletableFuture.join());
} catch (CompletionException e) {
     e.printStackTrace();
}

If you execute the above code, then the CompletionException is thrown by the finalCompletableFuture with cause java.lang.NullPointerException (exception throws by the provided CompletableFuture).

java.util.concurrent.CompletionException: java.lang.NullPointerException
	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:1766)
	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1756)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1016)
	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1665)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1598)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183)
Caused by: java.lang.NullPointerException
	at com.tedblob.java.CompletableFuture.CompletableFutureAllOf.lambda$main$0(CompletableFutureAllOf.java:20)

3. Conclusion

In this article, we have learned the purpose of the allOf method of the CompletableFuture.

Leave a Reply

Your email address will not be published. Required fields are marked *