186

I can't get my head around the difference between thenApply and thenCompose.

So, could someone provide a valid use case?

From the Java docs:

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.

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

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

I get that the 2nd argument of thenCompose extends the CompletionStage where thenApply does not.

Could someone provide an example in which case I have to use thenApply and when thenCompose?

Lii
  • 11,553
  • 8
  • 64
  • 88
GuyT
  • 4,316
  • 2
  • 16
  • 30
  • 63
    Do you understand the difference between `map` and `flatMap` in `Stream`? `thenApply` is the `map` and `thenCompose` is the `flatMap` of `CompletableFuture`. You use `thenCompose` to avoid having `CompletableFuture>`. – Misha Mar 25 '17 at 23:29
  • 1
    This is a very nice guide to start with CompletableFuture - https://www.baeldung.com/java-completablefuture – thealchemist May 19 '20 at 13:00

9 Answers9

248

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));
Joe C
  • 15,324
  • 8
  • 38
  • 50
  • 23
    Why should a programmer use `.thenCompose(x -> CompletableFuture.supplyAsync(() -> x+1))` instead of `.thenApplyAsync(x -> x+1)`? Being synchronous or asynchronous is *not* the relevant difference. – Holger Jan 22 '18 at 12:17
  • 34
    They would not do so like that. However, if a third-party library that they used returned a `CompletableFuture`, then this would be where `thenCompose` would be the way to flatten the structure. – Joe C Jan 22 '18 at 20:44
  • @Holger read my other answer if you're confused about `thenApplyAsync` because it is not what you think it is. – 1283822 Jul 27 '18 at 15:47
  • @1283822 I don’t know what makes you think that I was confused and there’s nothing in your answer backing your claim that “it is not what you think it is”. – Holger Jul 27 '18 at 15:55
  • @Holger Apologies. I meant it _might not_ be what you think it is. And you _may_ read it _if_ you're confused. I see mention of `thenApplyAsync` which doesn't have to do with the difference in this question. And my other answer is https://stackoverflow.com/questions/47489338/what-is-the-difference-between-thenapply-and-thenapplyasync-of-java-completablef#51560731 – 1283822 Jul 27 '18 at 16:01
  • 1
    I honestly thing that a better code example that has BOTH sync and async functions with BOTH .supplyAsync().thenApply() and .supplyAsync(). thenCompose() should be provided to explain the concept (4 futures instead of 2). – Oleksandr Berezianskyi Aug 15 '18 at 11:24
115

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.

Kamlesh
  • 95
  • 1
  • 3
  • 10
Dorjee
  • 1,533
  • 1
  • 12
  • 9
  • 16
    Then Joe C's answer is not misleading. It is correct and more concise. – koleS Oct 25 '18 at 13:29
  • 2
    Imho it is poor design to write CompletableFuture getUserInfo and CompletableFuture getUserRating(UserInfo) \\ instead it should be UserInfo getUserInfo() and int getUserRating(UserInfo) if I want to use it async and chain, then I can use ompletableFuture.supplyAsync(x => getUserInfo(userId)).thenApply(userInfo => getUserRating(userInfo)) or anything like this, it is more readable imho, and not mandatory to wrap ALL return types into CompletableFuture – user1694306 Dec 16 '19 at 22:11
  • 1
    @user1694306 Whether it is poor design or not depends on whether the user rating is contained in the `UserInfo` (then yes) or whether it has to be obtained separately, maybe even costly (then no). – glglgl Feb 11 '20 at 08:36
51

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.

Didier L
  • 18,905
  • 10
  • 61
  • 103
  • 18
    I wonder why they didn't name those functions `map` and `flatMap` in the first place. – Matthias Braun Jun 16 '17 at 09:08
  • 1
    @MatthiasBraun I think that's because `thenApply()` will simply call `Function.apply()`, and `thenCompose()` is a bit similar to composing functions. – Didier L Jun 16 '17 at 09:56
27

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.

Doppelganger
  • 20,114
  • 8
  • 31
  • 29
1283822
  • 1,832
  • 17
  • 13
  • While i understand the example given, i think thenApply((y)->System.println(y)); doesnt work. It takes a function,but a consumer is given. We should replac it with thenAccept(y)->System.println(y)) – Sriharsha g.r.v Aug 21 '21 at 04:42
4

thenCompose() is better for chaining CompletableFuture.

thenApply() is better for transform result of Completable future.

You can achieve your goal using both techniques, but one is more suitable for one use case then other.

public CompletableFuture<Integer> process(Integer i) {
    CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(
            () -> new HeavyTask(i).execute());
    return completableFuture;
}

@SneakyThrows
public CompletableFuture<Integer> thenApplyVsThenCompose() {
    // each time calling thenApply() function returns CompletionState
    // so you will get nested Futures 
    // you can think about it like map() java optional
    CompletableFuture<Future<Integer>> cf1 = CompletableFuture.supplyAsync(
            () -> new HeavyTask().execute())
            .thenApply(i -> process(i));

    // to get result you will have to get nested get() calls
    Integer resultFromThenApply = cf1.get().get();

    // when calling thenCompose() nested Futures are flatten
    // you can think about it like flatMap() java optional
    CompletableFuture<Integer> cf2;
    cf2 = CompletableFuture.supplyAsync(
            () -> new HeavyTask().execute())
            .thenCompose(this::process);

    // you have to just call one get() since thenCompose was flatten
    Integer resultFromThenCompose = cf2.get();
    return null;
} 

Other problem that can visualize difference between those two

How would you implement solution when you do not know how many time you have to apply thenApply()/thenCompose() (in case for example recursive methods)?

public void nested() throws ExecutionException, InterruptedException {
    CompletableFuture<Integer> completableFutureToCompose = CompletableFuture.completedFuture(1);
    for (int i = 0; i < 10; i++) {
        log.info("Composing");
        completableFutureToCompose = completableFutureToCompose.thenCompose(this::process);
    }
    completableFutureToCompose.get();

    // not achievable using then apply
    CompletableFuture<Integer> completableFutureToApply = CompletableFuture.completedFuture(1);
    for (int i = 0; i < 10; i++) {
        log.info("Applying");
        completableFutureToApply = completableFutureToApply.thenApply(this::process).get();
    }
    completableFutureToCompose.get();
}

public CompletableFuture<Integer> process(Integer i) {
    log.info("PROCESSING");
    CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(
            () -> new HeavyTask(i).execute());
    return completableFuture;
}
  • Using composing you first create receipe how futures are passed one to other and then execute
  • Using apply you execute logic after each apply invocation
MagGGG
  • 19,198
  • 2
  • 29
  • 30
  • When I run your second code, it have same result System.out.println("Applying"+completableFutureToApply.get()); and System.out.println("Composing"+completableFutureToCompose.get()); , the comment at end of your post about time of execute task is right but the result of get() is same, can you explain the difference , thank you – vuhoanghiep1993 Dec 22 '21 at 09:36
1

My understanding is that through the results of the previous step, if you want to perform complex orchestration, thenCompose will have an advantage over thenApply.

The following example is, through the results of the first step, go to two different places to calculate, whoever returns sooner, you can see the difference between them

    CompletableFuture<Integer> result = CompletableFuture.supplyAsync(() -> 1);
    // thenCompose
    System.out.println(result.thenCompose(i -> CompletableFuture.anyOf(CompletableFuture.supplyAsync(() -> i + 1), CompletableFuture.supplyAsync(() -> i + 2))).join());
    System.out.println(result.thenCompose(i -> CompletableFuture.supplyAsync(() -> i + 1).applyToEither(CompletableFuture.supplyAsync(() -> i + 2), j -> j)).join());
    // ----- thenApply
    System.out.println(result.thenApply(i -> CompletableFuture.anyOf(CompletableFuture.supplyAsync(() -> i + 1), CompletableFuture.supplyAsync(() -> i + 2)).join()).join());
    System.out.println(result.thenApply(i -> CompletableFuture.supplyAsync(() -> i + 1).applyToEither(CompletableFuture.supplyAsync(() -> i + 2), j -> j).join()).join());
0

JoeC's answer is correct, but I think the better comparison that can clear the purpose of the thenCompose is the comparison between thenApply and thenApply! Once when a synchronous mapping is passed to it and once when an asynchronous mapping is passed to it.

If the mapping passed to the thenApply returns an String(a non-future, so the mapping is synchronous), then its result will be CompletableFuture<String>. Now similarly, what will be the result of the thenApply, when the mapping passed to the it returns a CompletableFuture<String>(a future, so the mapping is asynchronous)? The end result will be CompletableFuture<CompletableFuture<String>>, which is unnecessary nesting(future of future is still future!). Here's where we can use thenCompose to be able to "compose"(nest) multiple asynchronous tasks in each other without getting futures nested in the result.

aderchox
  • 3,163
  • 2
  • 28
  • 37
0

private void test1() throws ExecutionException, InterruptedException {

    //thenApply返回的是之前的CompletableFuture
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 1)
            .thenApply((x) -> {
                x = x + 1;
                log.info("thenApply, 1, x:{}", x);
                return x;
            });

    System.out.println(future.get());
}

//thenCompose返回的是新的CompletableFuture
private void test2() throws ExecutionException, InterruptedException {
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 1)
            .thenCompose((x) -> {
                return CompletableFuture.supplyAsync(() -> {
                    Integer y = x + 1;
                    log.info("thenCompose, 1, x:{}", y);
                    return y;
                });
            });

    System.out.println(future.get());
}
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Nov 16 '21 at 09:11
0

And if you are still confused about what makes the real difference in code when I use thenApply vs thenCompose and what a nested future looks like then please look at the full working example.

package com.graphql.demo.garphqlapi;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

public class ComposeVsThenApply {

    public static void main(String[] args) {
        ComposeVsThenApply cva = new ComposeVsThenApply();
        //thenCompose usage : Using the thenCompose for simplifying the return type of dependent processing.
        System.out.println("Starting thenCompose demo");
        CompletableFuture<StockRating> flattenedFuture = cva.getStockDetails("Apple").thenCompose((stock) -> {
            return cva.getRating(stock);
        });
        //Retrive results
        try {
            StockRating stockViaThenCompose = flattenedFuture.get();
            System.out.println("\n\t\tStock summery :" + stockViaThenCompose.getStockName() + ", Rating :" + stockViaThenCompose.getRating());
            System.out.println("--- thenCompose demo ended ---");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        //ThenAply: thenApply is good for result transformation but sucks when we have two asynchronous dependent processing. Now you get nested future.
        System.out.println("\n\n\nStarting thenApply demo");
        CompletableFuture<CompletableFuture<StockRating>> nestedFuture = cva.getStockDetails("Apple").thenApply((stock) -> {
            return cva.getRating(stock);
        });
        //Retrive results
        try {
            StockRating stockrViaThenApply = nestedFuture.get().get();
            System.out.println("\n\t\tStock summery :" + stockrViaThenApply.getStockName() + ", Rating :" + stockrViaThenApply.getRating());
            System.out.println("--- thenApply demo ended---");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

    class Stock {
        private String name;

        public Stock(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }

    class StockRating {
        private Double rating;

        public boolean isBuyCall() {
            return buyCall;
        }

        public void setBuyCall(boolean buyCall) {
            this.buyCall = buyCall;
        }

        public String getStockName() {
            return stockName;
        }

        public void setStockName(String stockName) {
            this.stockName = stockName;
        }

        private boolean buyCall;

        public StockRating(Double rating, boolean buyCall, String stockName) {
            this.rating = rating;
            this.buyCall = buyCall;
            this.stockName = stockName;
        }

        private String stockName;

        public StockRating(Double rating) {
            this.rating = rating;
        }

        public Double getRating() {
            return rating;
        }

        public void setRating(Double rating) {
            this.rating = rating;
        }
    }

    class StockSupplier implements Supplier<Stock> {
        private String name;

        public StockSupplier(String name) {
            this.name = name;
        }

        @Override
        public Stock get() {
            try {
                System.out.println("\n\t\tRetriving details for " + this.name);
                TimeUnit.SECONDS.sleep(4);
                System.out.println("\n\t\tDone with details retrival for " + this.name);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return new Stock(name);
        }
    }

    class RatingSupplier implements Supplier<StockRating> {
        private Stock stock;

        public RatingSupplier(Stock stock) {
            this.stock = stock;
        }

        @Override
        public StockRating get() {
            try {
                System.out.println("\n\t\tRetriving stock rating for " + this.stock.getName());
                TimeUnit.SECONDS.sleep(4);
                System.out.println("\n\t\tDone with rating retrival for " + this.stock.getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return new StockRating(10d, true, this.stock.getName());
        }
    }

    public CompletableFuture<Stock> getStockDetails(String name) {
        return CompletableFuture.supplyAsync(new StockSupplier(name));
    }

    public CompletableFuture<StockRating> getRating(Stock stock) {
        return CompletableFuture.supplyAsync(new RatingSupplier(stock));
    }

    public String getSummeryReport(Stock stock, StockRating sr) {
        return stock.getName() + "/n " + sr.getRating();
    }
}
Amit Meena
  • 2,884
  • 2
  • 21
  • 33