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
Transform the result with thenApplyAsync
callbacks
- The
thenApplyAsync
takes and transforms the result returned fromsupplyAsync
@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 withAsync
suffix) and asynchronous execution with customExecutor
. The default async usesForkJoinPool.commonPool()
to execute. The following gives you an example of using customExecutor
@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 anotherCompletableFuture
@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 independentCompletableFuture
@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 newCompletableFuture<Void>
which completed when all of the givenCompletableFuture
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 newCompletableFuture<Object>
which completed when any of the givenCompletableFuture
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 suppliedConsumer
function and returns a newCompletionStage<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 suppliedRunnable
action when the stage completes normally and returns a newCompletionStage<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