
1. Overview
In this article, we will learn the differences between thenApply vs thenCompose methods of the CompletableFuture. To learn about other methods available in the CompletableFuture, refer to these articles.
2. CompletableFuture thenApply vs thenCompose
You can process and transform the result of the CompletableFuture
by using the thenApply and thenCompose methods.
The major difference lies in the return type of these methods. The thenApply function can return non-future types (like primitives, Object) as well as nested Future as output while thenCompose flattens the structure and returns a CompletableStage directly as the result. The CompletionStage<T>
is an interface of which CompletableFuture<T>
is the only current implementing class.
For example, the thenApply returns as a nested future while thenCompose flattens the structure returns the Future directly without nesting.
You can compare thenApply to map
and thenCompose to flatMap
.
CompletableFuture<CompletableFuture<Integer>> thenApplyFuture = numberFuture.thenApply(number -> { return calculateSquare(number) }); CompletableFuture<Integer> thenComposeFuture = numberFuture.thenCompose(number -> { return calculateSquare(number) });
Let’s see this difference with examples.
2.1. thenApply of CompletableFuture
The thenApply method accepts a Function
as an argument. The Function
receives the result of the CompletableFuture and then process it. You can write code that needs to be executed synchronously inside this function.
As discussed earlier, the thenApply can return non-future types as output.
Assume we have a CompletableFuture
that returns a natural number as result after performing some computations.
CompletableFuture<Integer> numberCompletableFuture = CompletableFuture.supplyAsync(() -> { // performs some computations System.out.println("numberCompletableFuture executes in : " + Thread.currentThread().getName()); return 5; });
Now, the following code in thenApply function gets the result (natural number) from the numberCompletableFuture
and calculates square of it. This thenApply waits for the numberCompletableFuture to complete its execution. Then immediately calculates the square and returns the Integer as value.
CompletableFuture<Integer> squareNumber = numberCompletableFuture.thenApply(number -> number * number);
If you execute the above code, then the following statements appear in the console:
numberCompletableFuture executes in : ForkJoinPool.commonPool-worker-3 squareNumberFuture executes in : ForkJoinPool.commonPool-worker-3
The thenApply can also return nested future.
CompletableFuture<CompletableFuture<Integer>> squareNumberFuture = numberCompletableFuture.thenApply(number -> { System.out.println("thenCompose executes in : " + Thread.currentThread().getName() + Thread.currentThread().isDaemon()); return calculateSquare(number); }); private CompletableFuture<Integer> calculateSquare(Integer number) { return CompletableFuture.supplyAsync(() -> { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("squareNumberFuture executes in : " + Thread.currentThread().getName()); return number * number; }); }
2.2. thenCompose of CompletableFuture
You can perform asynchronous processing by using the thenCompose which flattens the structure and returns a CompletableFuture
. Note that this can return only CompletableStage as the result. The CompletionStage<T>
is an interface of the implementation class CompletableFuture<T>
.
Let’s rewrite the square code by using the thenCompose
method. Note that the code inside the CompletableFuture
returned by the thenCompose
executes asynchronously.
CompletableFuture<Number> squareNumberFuture = numberCompletableFuture.thenCompose(number -> { System.out.println("thenCompose executes in : " + Thread.currentThread().getName()); return CompletableFuture.supplyAsync(() -> { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("squareNumberFuture executes in : " + Thread.currentThread().getName()); return number * number }); });
If you execute the above code, then the following statements are printed in the console:
numberCompletableFuture executes in : ForkJoinPool.commonPool-worker-3 thenCompose executes in : ForkJoinPool.commonPool-worker-3 squareNumberFuture executes in : ForkJoinPool.commonPool-worker-5
3. Conclusion
To sum up, we have learned the difference between the thenApply and thenCompose methods of the CompletableFuture with relevant examples.