What is the difference between thenApply and thenApplyAsync of Java CompletableFuture?

The difference has to do with the Executor that is responsible for running the code. Each operator on CompletableFuture generally has 3 versions.

  1. thenApply(fn) - runs fn on a thread defined by the CompleteableFuture on which it is called, so you generally cannot know where this will be executed. It might immediately execute if the result is already available.
  2. thenApplyAsync(fn) - runs fn on a environment-defined executor regardless of circumstances. For CompletableFuture this will generally be ForkJoinPool.commonPool().
  3. thenApplyAsync(fn,exec) - runs fn on exec.

In the end the result is the same, but the scheduling behavior depends on the choice of method.


You're mis-quoting the article's examples, and so you're applying the article's conclusion incorrectly. I see two question in your question:

What is the correct usage of .then___()

In both examples you quoted, which is not in the article, the second function has to wait for the first function to complete. Whenever you call a.then___(b -> ...), input b is the result of a and has to wait for a to complete, regardless of whether you use the methods named Async or not. The article's conclusion does not apply because you mis-quoted it.

The example in the article is actually

CompletableFuture<String> receiver = CompletableFuture.supplyAsync(this::findReceiver);

receiver.thenApplyAsync(this::sendMsg);  
receiver.thenApplyAsync(this::sendMsg);  

Notice the thenApplyAsync both applied on receiver, not chained in the same statement. This means both function can start once receiver completes, in an unspecified order. (Any assumption of order is implementation dependent.)

To put it more clearly:

a.thenApply(b).thenApply(c); means the order is a finishes then b starts, b finishes, then c starts.
a.thenApplyAsync(b).thenApplyAsync(c); will behave exactly the same as above as far as the ordering between a b c is concerned.

a.thenApply(b); a.thenApply(c); means a finishes, then b or c can start, in any order. b and c don't have to wait for each other.
a.thenApplyAync(b); a.thenApplyAsync(c); works the same way, as far as the order is concerned.

You should understand the above before reading the below. The above concerns asynchronous programming, without it you won't be able to use the APIs correctly. The below concerns thread management, with which you can optimize your program and avoid performance pitfalls. But you can't optimize your program without writing it correctly.


As titled: Difference between thenApply and thenApplyAsync of Java CompletableFuture?

I must point out that the people who wrote the JSR must have confused the technical term "Asynchronous Programming", and picked the names that are now confusing newcomers and veterans alike. To start, there is nothing in thenApplyAsync that is more asynchronous than thenApply from the contract of these methods.

The difference between the two has to do with on which thread the function is run. The function supplied to thenApply may run on any of the threads that

  1. calls complete
  2. calls thenApply on the same instance

while the 2 overloads of thenApplyAsync either

  1. uses a default Executor (a.k.a. thread pool), or
  2. uses a supplied Executor

The take away is that for thenApply, the runtime promises to eventually run your function using some executor which you do not control. If you want control of threads, use the Async variants.

If your function is lightweight, it doesn't matter which thread runs your function.

If your function is heavy CPU bound, you do not want to leave it to the runtime. If the runtime picks the network thread to run your function, the network thread can't spend time to handle network requests, causing network requests to wait longer in the queue and your server to become unresponsive. In that case you want to use thenApplyAsync with your own thread pool.


Fun fact: Asynchrony != threads

thenApply/thenApplyAsync, and their counterparts thenCompose/thenComposeAsync, handle/handleAsync, thenAccept/thenAcceptAsync, are all asynchronous! The asynchronous nature of these function has to do with the fact that an asynchronous operation eventually calls complete or completeExceptionally. The idea came from Javascript, which is indeed asynchronous but isn't multi-threaded.