Will a chain of method calls (CompletableFuture API) execute asynchronously if the first method in a chain is asynchronous?

OK, so there are 3 types of methods in CompletableFuture. For example:

  • thenApply()
  • thenApplyAsync(Function) (without an Executor)
  • thenApplyAsync(Function, Executor) (with an Executor)

The last one means that this action will execute in the Executor that you pass to it and it is the most obvious one.

The second one means that the action is executed in the ForkJoinPool.

The first one is far more interesting. The documentation makes it sound like it’s easy, via:

Actions supplied for dependent completions of non-async methods may be performed by the thread that completes the current CompletableFuture, or by any other caller of a completion method

And you need to start bisecting this into smaller pieces. What you need to understand that there are threads that complete a certain CompletableFuture, there are threads that execute some actions on it and there are threads that chain certain dependent actions. Potentially, these are all different threads. And this is where it all begins:

  • If the dependent action is already chained, the thread that will call complete is going to be the thread that executes this action.

  • If the future is already completed, then the thread that chains the action will execute it.

Since there is no linear actions on the steps above, it is literally impossible to say for sure in which thread your thenApply will execute, at least with 100% certainty. That action can be executed in any of :

  • the thread that calls complete/completeExceptionally
  • the thread that does the chaining of thenApply
  • the thread that calls join/get

Any of the above is a possibility. If you really want I made a rather interesting test here, proving some of the things above.


I am not trying to pick on the other answer, but he made a rather interesting point that I was very confused about in the begging too:

In your example: After .thenComposeAsync also all the following chained futures will get completed by executor.

We can easily prove that this is not correct:

 CompletableFuture<String> future1 = CompletableFuture.completedFuture("a");
 CompletableFuture<String> future2 = future1.thenApplyAsync(x -> "b" + x, Executors.newCachedThreadPool());

 LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));

 CompletableFuture<String> future3 = future2.thenApply(x -> {
      System.out.println(Thread.currentThread().getName());
      return x + "c";
 });
 future3.join();

What you are going to see if you run this, is that main actually executes thenApply, not a thread from the pool.

Leave a Comment