Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CompletableFuture | thenApply vs thenCompose

People also ask

What is difference between thenApply and thenCompose?

thenApply is the map and thenCompose is the flatMap of CompletableFuture . You use thenCompose to avoid having CompletableFuture<CompletableFuture<..>> .

What is the difference between future and CompletableFuture?

Future vs CompletableFuture. CompletableFuture is an extension to Java's Future API which was introduced in Java 5. A Future is used as a reference to the result of an asynchronous computation.

What is the CompletableFuture?

What is CompletableFuture? A CompltableFuture is used for asynchronous programming. Asynchronous programming means writing non-blocking code. It runs a task on a separate thread than the main application thread and notifies the main thread about its progress, completion or failure.

Is CompletableFuture join blocking?

Note that we do not have any try- catch block around the join method.


thenApply is used if you have a synchronous mapping function.

CompletableFuture<Integer> future = 
    CompletableFuture.supplyAsync(() -> 1)
                     .thenApply(x -> x+1);

thenCompose is used if you have an asynchronous mapping function (i.e. one that returns a CompletableFuture). It will then return a future with the result directly, rather than a nested future.

CompletableFuture<Integer> future = 
    CompletableFuture.supplyAsync(() -> 1)
                     .thenCompose(x -> CompletableFuture.supplyAsync(() -> x+1));

I think the answered posted by @Joe C is misleading.

Let me try to explain the difference between thenApply and thenCompose with an example.

Let's suppose that we have 2 methods: getUserInfo(int userId) and getUserRating(UserInfo userInfo):

public CompletableFuture<UserInfo> getUserInfo(userId)

public CompletableFuture<UserRating> getUserRating(UserInfo)

Both method return types are CompletableFuture.

We want to call getUserInfo() first, and on its completion, call getUserRating() with the resulting UserInfo.

On the completion of getUserInfo() method, let's try both thenApply and thenCompose. The difference is in the return types:

CompletableFuture<CompletableFuture<UserRating>> f =
    userInfo.thenApply(this::getUserRating);

CompletableFuture<UserRating> relevanceFuture =
    userInfo.thenCompose(this::getUserRating);

thenCompose() works like Scala's flatMap which flattens nested futures.

thenApply() returned the nested futures as they were, but thenCompose() flattened the nested CompletableFutures so that it is easier to chain more method calls to it.


The updated Javadocs in Java 9 will probably help understand it better:

thenApply

<U> CompletionStage<U> thenApply​(Function<? super T,? extends U> fn)

Returns a new CompletionStage that, when this stage completes normally, is executed with this stage's result as the argument to the supplied function.

This method is analogous to Optional.map and Stream.map.

See the CompletionStage documentation for rules covering exceptional completion.

thenCompose

<U> CompletionStage<U> thenCompose​(Function<? super T,? extends CompletionStage<U>> fn)

Returns a new CompletionStage that is completed with the same value as the CompletionStage returned by the given function.

When this stage completes normally, the given function is invoked with this stage's result as the argument, returning another CompletionStage. When that stage completes normally, the CompletionStage returned by this method is completed with the same value.

To ensure progress, the supplied function must arrange eventual completion of its result.

This method is analogous to Optional.flatMap and Stream.flatMap.

See the CompletionStage documentation for rules covering exceptional completion.


thenApply and thenCompose are methods of CompletableFuture. Use them when you intend to do something to CompletableFuture's result with a Function.

thenApply and thenCompose both return a CompletableFuture as their own result. You can chain multiple thenApply or thenCompose together. Supply a Function to each call, whose result will be the input to the next Function.

The Function you supplied sometimes needs to do something synchronously. The return type of your Function should be a non-Future type. In this case you should use thenApply.

CompletableFuture.completedFuture(1)
    .thenApply((x)->x+1) // adding one to the result synchronously, returns int
    .thenApply((y)->System.println(y)); // value of y is 1 + 1 = 2

Other times you may want to do asynchronous processing in this Function. In that case you should use thenCompose. The return type of your Function should be a CompletionStage. The next Function in the chain will get the result of that CompletionStage as input, thus unwrapping the CompletionStage.

// addOneAsync may be implemented by using another thread, or calling a remote method
abstract CompletableFuture<Integer> addOneAsync(int input);

CompletableFuture.completedFuture(1)
    .thenCompose((x)->addOneAsync(x)) // doing something asynchronous, returns CompletableFuture<Integer>
    .thenApply((y)->System.println(y)); // y is an Integer, the result of CompletableFuture<Integer> above

This is a similar idea to Javascript's Promise. Promise.then can accept a function that either returns a value or a Promise of a value. The reason why these two methods have different names in Java is due to generic erasure. Function<? super T,? extends U> fn and Function<? super T,? extends CompletionStage<U>> fn are considered the same Runtime type - Function. Thus thenApply and thenCompose have to be distinctly named, or Java compiler would complain about identical method signatures. The end result being, Javascript's Promise.then is implemented in two parts - thenApply and thenCompose - in Java.

You can read my other answer if you are also confused about a related function thenApplyAsync.