The Future is Completable in Java 8

As I mentioned in my previous post, interface
Future in Java up to version 7 has a serious limitation, and that is that it’s not possible to get notified when the result is available. The only thing you can do with a
Future is call
get() on it, which will block if the result is not yet available, or you can check if it’s ready (polling) by calling the
isDone() method.

But both of these things are not what you want – if you want your application to be scalable you don’t want to waste resources by issuing blocking calls, and polling also not ideal. What you really want is attach a callback to the
Future to process the result when it’s available.

In Java 8, a new and powerful implementation of interface
Future was added: class
CompletableFuture, which allows you to attach callbacks and much more – in fact, it allows you to build a pipeline of steps which can each be executed asynchronously, each step depending on the result of one or more previous steps.

The API of
CompletableFuture is quite elaborate, it’s API documentation specifies no less than 59 methods, so it’s not easy to understand when you should use which of the methods. In this post I’ll organize and explain the API of
CompletableFuture.

CompletionStage

Let’s first have a look at
CompletionStage, the superinterface of
CompletableFuture, which declares most of the methods that comprise the functionality of
CompletableFuture.

A stage of a possibly asynchronous computation, that performs an action or computes a value when another CompletionStage completes. A stage completes upon termination of its computation, but this may in turn trigger other dependent stages.

An example of what this might be useful for: Suppose you have an online service which needs to do a database query and a call to a web service and combine the results of both into a web page. You could do this in the straightforward way: first do a database query, then send a request to the web service and then assemble the results into a web page. It would be faster, however, if you could issue the database query and the request to the web service concurrently, and then assemble the web page when both return a result. You can do that with completion stages.

Organization of interface CompletionStage

The interface defines 38 methods, which are organized in 3 blocks of 3 methods which each have 3 variants, plus four more general handler methods (of which three come in 3 variants) and one other method: (3 x 3 x 3) + (3 x 3 + 1) + 1 = 38 methods.

The grouping of the 3 x 3 x 3 methods is done by the following orthogonal aspects:

Whether the stage is triggered by the completion of a single previous stage, the completion of both of two previous stages (AND), or the completion of either of two previous stages (OR).

Whether the computation takes an argument or not and returns a result or not.

How the execution of the computation is arranged (synchronous or asynchronous, or with a custom
Executor).

The names of the methods reflect which of these aspects applies to each method (although the naming is not entirely consistent).

The first aspect (what triggers a stage):

Methods with names starting with “then” are for adding another stage to be triggered when a single stage completes.

Methods with names containing “both” are for adding another stage to be triggered when two previous stages both complete.

Methods with names containing “either” are for adding another stage to be triggered when either one of two previous stages completes.

The second aspect (whether the computation takes an argument and returns a result):

Methods with names containing “apply” take a
Function, which takes an argument (the result of the previous stage) and return a result (the argument for the next stage).

Methods with names containing “accept” take a
Consumer, which takes an argument but does not return a result.

Methods with names containing “run” take a
Runnable, which takes no arguments and does not return a result.

The third aspect (how the execution of the computation is arranged):

Methods with names which do not end in “async” execute the computation using the stage’s default execution facility.

Methods with names that end in “async” execute the computation using the stage’s default asynchronous execution facility.

Methods with names that end in “async” and that also take an
Executor argument, execute the computation using the specified
Executor.

How exactly a computation runs with the “default” and “default asynchronous” execution model is not specified by interface
CompletionStage, but is left up to the implementation.

For
CompletableFuture, “default” means: execute in the thread that completes the previous stage, and “default asynchronous” means: execute using the default thread pool of the fork-join framework (the executor returned by
ForkJoinPool.commonPool()).

Block 1 – triggered by a single previous stage

Methods

Takes a

Returns

thenApply(Async)

Function

CompletionStage holding the result of the
Function

thenAccept(Async)

Consumer

CompletionStage<Void>

thenRun(Async)

Runnable

CompletionStage<Void>

Block 2 – triggered by both of two previous stages

Each of these methods take another
CompletionStage and one of:
BiFunction,
BiConsumer or
Runnable.

Methods

Takes a

Returns

thenCombine(Async)

BiFunction

CompletionStage holding the result of the
Function

thenAcceptBoth(Async)

BiConsumer

CompletionStage<Void>

runAfterBoth(Async)

Runnable

CompletionStage<Void>

Note the irregularity in the method names here: if the naming would have been regular,
thenCombine should have been named
applyToBoth and
thenAcceptBoth should have been named
acceptBoth. The irregularity is curious, especially since in block 3 the methods do have the expected names.

Note about exception handling: When both the previous stages result in an exception, then it’s not specified which of the two exceptions gets propagated to the dependent stage.

Block 3 – triggered by either one of two previous stages

Each of these methods take another
CompletionStage and one of:
Function,
Consumer or
Runnable.

Methods

Takes a

Returns

applyToEither(Async)

Function

CompletionStage holding the result of the
Function

acceptEither(Async)

Consumer

CompletionStage<Void>

runAfterEither(Async)

Runnable

CompletionStage<Void>

Note about exception handling: It’s not specified which result of the previous two stages is propagated to the dependent stage. For example, if one previous stage results in an exception and the other one results in a value, then the dependent stage could receive either an exception or a value.

More general handler methods

Besides the three blocks, there are four more general handler methods, that also let you deal with exceptions that might be thrown during a computation.

The
thenCompose(Async) methods (3 variants) are similar to
thenApply(Async), except that the
Function passed to
thenCompose returns a
CompletionStage containing the result of the computation instead of returning the result itself. You’ll most likely not need to use
thenCompose a lot.

The
whenComplete(Async) methods (3 variants) take a
BiConsumer which is passed two arguments: the value returned by the previous stage and a
Throwable. One of these two arguments will contain a value and the other one will be
null, depending on whether the previous step completed normally or with an exception. The
CompletionStage returned by
whenComplete preserves the result of the previous step (unless the
BiConsumer throws an exception and the previous stage didn’t throw an exception – then the returned
CompletionStage is completed with the exception thrown by the
BiConsumer).

The
handle(Async) methods (3 variants) are almost the same as the
whenComplete(Async) methods, except that they take a
BiFunction instead of a
BiConsumer, and instead of preserving the result of the previous stage, they return a
CompletionStage with the result of the specified
BiFunction.

Finally, there’s the method
exceptionally (of which there is only one variant, not three) specifically for dealing with exceptions. It takes a
Function which gets the exception as its argument, and which returns a value to be returned instead of the exception.

The final method

The last method is
toCompletableFuture, which returns the
CompletionStage as a
CompletableFuture.

CompletableFuture

Interface
CompletionStage specifies most of the functionality of
CompletableFuture. Class
CompletableFuture adds a number of extra methods:

Implementations for the five methods of interface
Future.

Methods
complete and
completeExceptionally, to complete the future with a value or an exception.

Somewhat dangerous methods
obtrudeValue and
obtrudeException, to overwrite the result of the future even when it was previously already completed with a value or exception. (Dangerous because other threads might already have seen the previous result – using these methods could lead to hard to pin down concurrency bugs).

A method
getNow which doesn’t block, but returns a fallback value when the result is not yet available.

A method
join which is the same as get, except that it doesn’t throw any checked exceptions, which makes it more useful for use in lambda expressions.

A method
isCompletedExceptionally which checks if the future was completed with an exception.