It blocks on runAsync inside thenComposeAsync . thenComposeAsync launches the supplied function in the stream inside the executor e. But the function that you gave it is trying to execute the runAsync body itself inside the same executor.
You can better understand what happens by adding another trace output:
CompletableFuture.runAsync(() -> { System.out.println("Task 1. Thread: " + Thread.currentThread().getId()); }, e).thenComposeAsync((Void unused) -> { System.out.println("Task 1 1/2. Thread: " + Thread.currentThread().getId()); return CompletableFuture.runAsync(() -> { System.out.println("Task 2. Thread: " + Thread.currentThread().getId()); }, e); }, e).join();
Now, if you run it using a 2-thread artist, you will see that Task 1 1/2 and Task 2 work on different threads.
To fix this, replace thenComposeAsync with a regular thenCompose . I'm not sure why you ever used thenComposeAsync . If you have a method that returns CompletableFuture , this method is presumably not blocked and should not be executed asynchronously.
source share