ListenableFuture vs CompletableFuture – a comparison

In my previous blog post I wrote about how Google Guava’s ListenableFuture is an improvement over Java 6’s Future class. But Java 8 ships with a CompletableFuture class that brings much of the same benefits into the standard Java API. The below code example (again with inline comments) shows how to use CompletableFuture to implement the same logic of first looking up a userId and then converting it into a username, both asynchronously

public class CompletableFutureExample1 {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // Step 1: Create a regular thread pool based Executor
        ExecutorService executor = Executors.newFixedThreadPool(5);

        // Step 2: Provide a piece of logic that will be executed asynchronously within the above executor, and whose
        // result is encapsulated within a CompletableFuture
        CompletableFuture<UserId> userIdFuture = CompletableFuture.supplyAsync(() -> new UserId(1), executor);

        // Step 3: Connect the output of the userIdFuture to logic that converts it into userDetails,
        // again asynchronously
        CompletableFuture<UserDetails> userDetailsFuture =
                userIdFuture.thenApplyAsync(userId -> new UserDetails(userId), executor);

        // Step 4: Once the userDetails is computed, print out the username, again asynchronously
        CompletableFuture<Void> voidCompletableFuture =
                userDetailsFuture.thenAcceptAsync(userDetails -> System.out.println(userDetails.getUsername()), executor);

        // Step 5: Wait for the final step to complete:
        voidCompletableFuture.get();

        executor.shutdownNow();
    }

    private static class UserId {
        private long id;
        public UserId(long id) {
            this.id = id;
        }
    }

    private static class UserDetails {
        private UserId userId;

        public UserDetails(UserId userId) {
            this.userId = userId;
        }

        public String getUsername() {
            // this can be replaced by a lookup based on userId
            return "some username";
        }
    }
}

Thanks to the “fluent” nature of the CompletableFuture methods, the above steps can be combined into a single statement as below, without losing any of the type safety:

CompletableFuture.
        supplyAsync(() -> new UserId(1), executor).
        thenApplyAsync(userId -> new UserDetails(userId), executor).
        thenAcceptAsync(userDetails -> System.out.println(userDetails.getUsername()), executor).
        get();

CompletableFuture is implemented in an interesting way, by combining the pre-existing Future interface with a newly introduced (in Java 8) CompletionStage interface. CompletionStage tries to accommodate all the ways in which two pieces of logic can be combined together: synchronously/asynchronously, where they each may be functions that take and/or return values. This leads to an explosion of methods in the interface, with names like: thenAccept, thenAcceptAsync, thenApply, thenApplyAsync, thenRun, thenRunAsync… and so on.

Doug Lea, the author of this piece of code in the JDK (and also of a bulk of Java’s concurrency classes) outlines the reasons for this in an email from 2016 (link). According to him, CompletableFuture implements the entire mammoth interface of CompletionStage, but you can create subclasses that disable certain features of the same, by just throwing UnsupportedOperationException appropriately. As of now, CompletableFuture is the only subclass of CompletionStage within the JDK itself!

CompletableFuture also has a few static methods like “anyOf”, “allOf” that check for the completion of a list of provided futures. Lastly, Java 8 also provides an ExecutorCompletionService that can be used to track the status of a list of submitted Futures and trigger an action when all of them succeed/fail etc.

By no means is this an exhaustive description of CompletableFuture. There are aspects of error handling, and explicit completion that are not covered in this blog post.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s