Since Java 8, you can run a method asynchronously by using CompletableFuture.supplyAsync if you want to do something with the returning result or using CompletableFuture.runAsync if you don't

Apart from that, you can also use lambda statements, lambda expressions, and method references to provide the parameters of runAsync and supplyAsync methods

Let's walk through this tutorial to see the detail examples

runAsync has no return values while supplyAsync has

  • CompletableFuture.runAsync accepts a Runnable functional interface and return a CompletableFuture<Void> which doesn't have value
@Test
public void runAsync() throws ExecutionException, InterruptedException {  
    CompletableFuture c = CompletableFuture
        .runAsync(() -> System.out.println("runAsync has no return values"));

    assertThat(c.get()).isNull();
}


  • CompletableFuture.supplyAsync accepts a Supplier<U> functional interface and return a CompletableFuture<U>
@Test
public void supplyAsync() throws ExecutionException, InterruptedException {  
    CompletableFuture c = CompletableFuture
        .supplyAsync(() -> "supplyAsync has return value");

    assertThat(c.get()).isEqualTo("supplyAsync has return value");
}

runAsync and supplyAsync callbacks chain

  • You can attach thenRun* to the runAsync callbacks chain
@Test
public void runAsyncWithCallbacks() throws ExecutionException, InterruptedException {  
    CompletableFuture c = CompletableFuture
        .runAsync(() -> System.out.println("runAsync"))
        .thenRunAsync(() -> System.out.println("callback"));

    assertThat(c.get()).isNull();
}


  • You can attach thenApply*, thenCombine*, thenCompose*, thenAccept* and thenRun* to the supplyAsync callbacks chain
@Test
public void supplyAsyncWithCallbacks() throws ExecutionException, InterruptedException {  
    CompletableFuture c = CompletableFuture
        .supplyAsync(() -> "supplyAsync")
        .thenApplyAsync((s) -> s + " callback");

    assertThat(c.get()).isEqualTo("supplyAsync callback");
}

Run runAsync and supplyAsync with an ExecutorService

Internally, CompletableFuture.runAsync(Runnable) and CompletableFuture.supplyAsync(Supplier) uses ForkJoinPool.commonPool() to execute

  • You can provision a custom Executor to CompletableFuture.runAsync(Runnable, Executor) as the following
@Test
public void runAsyncWithExecutor() throws ExecutionException, InterruptedException {  
    CompletableFuture c = CompletableFuture
        .runAsync(() -> System.out.println("Run runAsync with an Executor"), executorService);

    stop(executorService);

    assertThat(c.get()).isNull();
}


  • You can also provision a custom Executor to CompletableFuture.supplyAsync(Supplier, Executor)
@Test
public void supplyAsyncWithExecutor() throws ExecutionException, InterruptedException {  
    CompletableFuture c = CompletableFuture
        .supplyAsync(() -> "run supplyAsync with an Executor", executorService);

    stop(executorService);

    assertThat(c.get()).isEqualTo("run supplyAsync with an Executor");
}

Conclusion

In this tutorial, we explored the difference, how to use CompletableFuture.runAsync and CompletableFuture.supplyAsync to run a Runnable or Supplier method asynchronously. You can find the full source code as below