Since Java 8, you can use
CompletableFuture.supplyAsync
to asynchronously run a task/method as the following example
@Test
public void supplyAsync() throws ExecutionException, InterruptedException {
CompletableFuture completableFuture = CompletableFuture.supplyAsync(() -> "Hello future!");
assertThat(completableFuture.get()).isEqualTo("Hello future!");
The CompletableFuture.supplyAsync
run asynchronously a Supplier
functional interface which represented by the lambda expression () -> "Hello future!"
The completableFuture.get()
blocks until the completableFuture
is complete and return the result
Apart from using get()
, you can also use its result to continuously execute other methods in the callbacks chain
Let's walk through this tutorial to see the examples in practice
The thenApplyAsync
takes and transforms the result returned from supplyAsync
@Test
public void thenApplyAsync() throws ExecutionException, InterruptedException {
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> "Future");
completableFuture = completableFuture.thenApplyAsync((s) -> s.concat(" is awesome!"));
assertThat(completableFuture.get()).isEqualTo("Future is awesome!");
APIs in CompletableFuture
may come in 3 forms: default synchronous execution, default asynchronous execution (name with Async
suffix) and asynchronous execution with custom Executor
. The default async uses ForkJoinPool.commonPool()
to execute. The following gives you an example of using custom Executor
@Test
public void thenApplyAsyncWithExecutor() throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(2);
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> "Future", executorService);
completableFuture = completableFuture.thenApplyAsync((s) -> s.concat(" is awesome!"), executorService);
stop(executorService);
assertThat(completableFuture.get()).isEqualTo("Future is awesome!");
Combine the results with thenComposeAsync
and thenCombineAsync
The thenComposeAsync
passes the result returned from one to another CompletableFuture
@Test
public void thenComposeAsync() throws ExecutionException, InterruptedException {
CompletableFuture<String> composedCompletableFuture = CompletableFuture
.supplyAsync(() -> "Future")
.thenComposeAsync(s -> CompletableFuture.supplyAsync(() -> s.concat(" is awesome!")));
assertThat(composedCompletableFuture.get()).isEqualTo("Future is awesome!");
The thenCombineAsync
combines the result of two independent CompletableFuture
@Test
public void thenCombineAsync() throws ExecutionException, InterruptedException {
CompletableFuture<String> completableFuture1 = CompletableFuture.supplyAsync(() -> "Future");
CompletableFuture<String> completableFuture2 = CompletableFuture.supplyAsync(() -> " is awesome!");
CompletableFuture<String> combinedCompletableFuture = completableFuture1.thenCombineAsync(completableFuture2, (s1, s2) -> s1.concat(s2));
assertThat(combinedCompletableFuture.get()).isEqualTo("Future is awesome!");
Await completion of independent CompletableFutures
The allOf
returns a new CompletableFuture<Void>
which completed when all of the given CompletableFuture
completed
@Test
public void allOf() {
CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> "Future");
CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(() -> " is awesome!");
CompletableFuture<String> cf3 = CompletableFuture.supplyAsync(() -> "!");
CompletableFuture<String>[] cfs = new CompletableFuture[]{cf1, cf2, cf3};
CompletableFuture<Void> allCf = CompletableFuture.allOf(cfs);
allCf.join();
String result = Arrays.stream(cfs).map(CompletableFuture::join).collect(Collectors.joining());
assertThat(result).isEqualTo("Future is awesome!!");
The anyOf
returns a new CompletableFuture<Object>
which completed when any of the given CompletableFuture
completed
@Test
public void anyOf() throws ExecutionException, InterruptedException {
CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> "Future");
CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(() -> " is awesome!");
CompletableFuture<String> cf3 = CompletableFuture.supplyAsync(() -> "!");
CompletableFuture<Object> anyCf = CompletableFuture.anyOf(cf1, cf2, cf3);
System.out.println(anyCf.get());
assertThat(anyCf).isDone();
Complete the stage with thenAcceptAsync
and thenRunAsync
The thenAcceptAsync
accepts the result returned from this stage, passes it to the supplied Consumer
function and returns a new CompletionStage<Void>
@Test
public void thenAcceptAsync() throws ExecutionException, InterruptedException {
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> "Future");
completableFuture = completableFuture.thenApplyAsync((s) -> s.concat(" is awesome!"));
CompletableFuture<Void> procedureFuture = completableFuture.thenAcceptAsync(System.out::println);
assertThat(procedureFuture.get()).isNull();
The thenRunAsync
runs the supplied Runnable
action when the stage completes normally and returns a new CompletionStage<Void>
@Test
public void thenRunAsync() throws ExecutionException, InterruptedException {
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> "Future");
completableFuture = completableFuture.thenApplyAsync((s) -> s.concat(" is awesome!"));
CompletableFuture<Void> procedureFuture = completableFuture.thenRunAsync(() -> System.out.println("!"));
assertThat(procedureFuture.isDone()).isTrue();
assertThat(procedureFuture.get()).isNull();
Conclusion
In this tutorial, we learned using CompletableFuture.supplyAsync
to run asynchronously a method and attach thenApplyAsync
, thenComposeAsync
, thenCombineAsync
, thenAcceptAsync
, thenRunAsync
to its callbacks chain. You can find the full source code as below