Java 8 CompletableFuture Tutorial with Examples
Introduction
This tutorial explains CompletableFuture, the class Java 8 added tojava.util.concurrent for writing asynchronous code without blocking threads. We will start with the limitations of the older Future interface that CompletableFuture was designed to remove. We will then see how to start asynchronous tasks with supplyAsync() and runAsync(), how to build a pipeline of dependent steps with thenApply(), thenAccept() and thenRun(), and how to get the result with join() or get(). Lastly, we will look at completing a future by hand, choosing the thread pool, and the async variants of each method.(Combining several futures and handling errors are covered in the follow-up tutorial, CompletableFuture: Combining and Error HandlingClick to read tutorial: Java 8 CompletableFuture: Combining and Error Handling.)
The problem with Future
Java 5 introducedFuture<T> as the result of a task submitted to an ExecutorServiceClick to read: ExecutorService. A Future represents a result that will be available later, but it offers only a few ways to use that result -
- Blocking only:
get()blocks the calling thread until the result is ready. There is no way to say "when the result arrives, do this with it". - No chaining: a second step that needs the first result has to wait for it with
get()and then submit another task by hand. - No combining: waiting for two or ten futures, or for the first of several to finish, needs hand-written loops.
- No manual completion: a
Futurecan only be completed by the task that created it. - No error pipeline: an exception only appears, wrapped in an
ExecutionException, when someone callsget().
What is CompletableFuture
CompletableFuture<T> implements two interfaces: Future<T>, so it can still be used wherever a Future is expected, and the new CompletionStage<T>, which defines about 40 methods for attaching the next step to a result that is not ready yet. Each of these methods takes a functional interfaceClick to read: functional interface - usually written as a lambda expressionClick to read: lambda expression - and returns a new CompletableFuture for the result of that step. Steps can therefore be chained into a pipeline, much like the operations of a stream.
Methods to create and run a CompletableFuture
Definition of the static factory methods injava.util.concurrent.CompletableFuture<T> -static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
AND
static CompletableFuture<Void> runAsync(Runnable runnable)supplyAsync()runs a SupplierClick to read: Supplier on another thread and completes the returned future with its value.runAsync()runs aRunnablethat returns nothing; its future completes withnullwhen the task ends.- Both have an overload taking an
Executoras a second argument. Without it, tasks run inForkJoinPool.commonPool(), the same shared pool that parallel streams use. completedFuture(value)returns a future that is already complete, andnew CompletableFuture<>()creates an empty one to be completed later withcomplete(value).
Methods to attach the next step
thenApply(Function<T,U>): transforms the result, returningCompletableFuture<U>- the CompletableFuture equivalent ofmap().thenAccept(Consumer<T>): uses the result without producing a new one, returningCompletableFuture<Void>.thenRun(Runnable): runs an action after completion without even looking at the result.join()andget(): wait for the result.join()throws an uncheckedCompletionExceptionon failure, so it is convenient in lambdas and streams;get()throws the checkedInterruptedExceptionandExecutionException, and has a timeout overload.
Java 8 code example of CompletableFuture basics
The example looks up an employee through a slow method (it sleeps for 200 milliseconds to imitate a database or remote call), then builds several pipelines on the result.Employee.java
//Employee.java
public class Employee {
private final String name;
private final double monthlySalary;
public Employee(String name, double monthlySalary) {
this.name = name;
this.monthlySalary = monthlySalary;
}
public String getName() { return name; }
public double getMonthlySalary() { return monthlySalary; }
}
Java 8 code showing supplyAsync, thenApply, thenAccept, thenRun and complete
//CompletableFutureBasics.java
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class CompletableFutureBasics {
// Imitates a slow lookup, such as a database query or a remote call
static Employee findEmployee(String id) {
sleep(200);
return new Employee("Tom Jones", 5200);
}
static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public static void main(String[] args) throws Exception {
// 1. Start a task asynchronously and collect the result later
CompletableFuture<Employee> lookup =
CompletableFuture.supplyAsync(() -> findEmployee("E-1001"));
System.out.println("1. Lookup started, done yet? " + lookup.isDone());
System.out.println(" Found: " + lookup.join().getName());
// 2. A pipeline: each stage runs when the previous one completes
CompletableFuture<String> annual = CompletableFuture
.supplyAsync(() -> findEmployee("E-1001"))
.thenApply(e -> e.getMonthlySalary() * 12)
.thenApply(salary -> String.format("Annual salary: %.2f", salary));
System.out.println("2. " + annual.join());
// 3. thenAccept and thenRun end a pipeline with side effects
CompletableFuture<Void> done = CompletableFuture
.supplyAsync(() -> findEmployee("E-1001"))
.thenAccept(e -> System.out.println("3. Sending payslip to " + e.getName()))
.thenRun(() -> System.out.println(" Payslip run finished"));
done.join();
// 4. A future completed by hand, from another thread
CompletableFuture<String> approval = new CompletableFuture<>();
new Thread(() -> {
sleep(100);
approval.complete("approved");
}).start();
System.out.println("4. Manager decision: " + approval.get(1, TimeUnit.SECONDS));
// 5. Running the task in our own thread pool instead of the common pool
ExecutorService payrollPool = Executors.newFixedThreadPool(2);
try {
String name = CompletableFuture
.supplyAsync(() -> findEmployee("E-1001").getName(), payrollPool)
.thenApply(String::toUpperCase)
.join();
System.out.println("5. With our own pool: " + name);
} finally {
payrollPool.shutdown();
}
// 6. An already completed future, useful for caches and tests
CompletableFuture<String> cached = CompletableFuture.completedFuture("Tom Jones");
System.out.println("6. Completed at once? " + cached.isDone()
+ ", value: " + cached.join());
}
}
1. Lookup started, done yet? false Found: Tom Jones 2. Annual salary: 62400.00 3. Sending payslip to Tom Jones Payslip run finished 4. Manager decision: approved 5. With our own pool: TOM JONES 6. Completed at once? true, value: Tom Jones
Explanation of the code
- Part 1:
supplyAsync()returns at once, whilefindEmployee()is still sleeping on a pool thread - soisDone()isfalse.join()then waits for the result. - Part 2: two
thenApply()calls turn aCompletableFuture<Employee>into aCompletableFuture<Double>and then into aCompletableFuture<String>. The whole pipeline is set up before any of it has run; the main thread only waits atjoin(). - Part 3:
thenAccept()consumes the employee, andthenRun()runs after it. The resultingCompletableFuture<Void>is joined only to keepmain()from ending before the messages are printed. - Part 4: an empty
CompletableFutureis completed by another thread withcomplete(). This is how callback-based APIs, such as a message listener, can be turned into a CompletableFuture.get(1, TimeUnit.SECONDS)waits at most one second. - Part 5: passing an
ExecutortosupplyAsync()runs the task in that pool. Use your own pool for slow or blocking work such as I/O, so that it does not tie up the common pool that parallel streams and other code share. A method reference,String::toUpperCase, works as a stage just like a lambda. - Part 6:
completedFuture()is complete from the start, which makes it easy to return a cached value from a method whose signature returns a CompletableFuture.
thenApply versus thenApplyAsync
Every step method has three versions: for examplethenApply(fn), thenApplyAsync(fn) and thenApplyAsync(fn, executor). The difference is which thread runs fn -
thenApply()runs the function in the thread that completed the previous stage, or, if that stage is already complete whenthenApply()is called, in the calling thread. It is the cheapest option for short, non-blocking steps.thenApplyAsync()always hands the function to the common pool (or to the given executor). Use it when a step is slow, so that it does not hold up the thread that completed the previous stage.
Best practices
- Keep the pipeline non-blocking: call
join()orget()only at the very end, where a result is really needed - not between stages. - Use your own executor for blocking I/O, and size it for the I/O, not for the number of CPUs.
- Always shut down executors you create, as part 5 does, or the JVM may not exit.
- Handle failures: an exception in any stage skips the remaining stages. The follow-up tutorial shows
exceptionally()andhandle().
Summary
In this tutorial we first saw the limitations of Java 5'sFuture: blocking get(), and no chaining, combining or manual completion. Next we saw that CompletableFuture implements both Future and CompletionStage, and how each stage returns a new future. We then covered the methods to create futures (supplyAsync(), runAsync(), completedFuture(), complete()) and to chain steps (thenApply(), thenAccept(), thenRun()), and ran an example of each, including a custom thread pool. Lastly, we compared thenApply() with thenApplyAsync() and listed best practices.Java 8 Tutorials
Java 8 CompletableFuture: Combining and Error HandlingClick to read tutorial: Java 8 CompletableFuture: Combining and Error Handling Java 8 Parallel Streams Tutorial with ExamplesClick to read tutorial: Java 8 Parallel Streams Tutorial with Examples Java 8 Collectors.toMap Tutorial with ExamplesClick to read tutorial: Java 8 Collectors.toMap Tutorial with Examples Java 8 Custom Collector with Collector.of() TutorialClick to read tutorial: Java 8 Custom Collector with Collector.of() Tutorial Java 8 Map merge, putIfAbsent and forEach with ExamplesClick to read tutorial: Java 8 Map merge, putIfAbsent and forEach with Examples Java 8 - How to Sort a Map by Key or ValueClick to read tutorial: Java 8 - How to Sort a Map by Key or Value Java 8 BiFunction, BiConsumer and BiPredicate TutorialClick to read tutorial: Java 8 BiFunction, BiConsumer and BiPredicate Tutorial
Java 8 CompletableFuture: Combining and Error HandlingClick to read tutorial: Java 8 CompletableFuture: Combining and Error Handling Java 8 Parallel Streams Tutorial with ExamplesClick to read tutorial: Java 8 Parallel Streams Tutorial with Examples Java 8 Collectors.toMap Tutorial with ExamplesClick to read tutorial: Java 8 Collectors.toMap Tutorial with Examples Java 8 Custom Collector with Collector.of() TutorialClick to read tutorial: Java 8 Custom Collector with Collector.of() Tutorial Java 8 Map merge, putIfAbsent and forEach with ExamplesClick to read tutorial: Java 8 Map merge, putIfAbsent and forEach with Examples Java 8 - How to Sort a Map by Key or ValueClick to read tutorial: Java 8 - How to Sort a Map by Key or Value Java 8 BiFunction, BiConsumer and BiPredicate TutorialClick to read tutorial: Java 8 BiFunction, BiConsumer and BiPredicate Tutorial