What is Java CompletableFuture?
Java CompletableFuture is used when you have a task that needs to be run asynchronously and you want to wait for the task to complete and obtain a value OR to force a task to complete. It also allows chaining multiple tasks together or executing a task after a few other tasks are completed. The power of Java CompletableFuture lies in its ability to Join tasks in multiple ways thereby providing a high level of flexibility in organizing asynchronous tasks.
It has been added in Java 1.8 and extends the functionality of Future.
Example of creating a Java CompletableFuture.
Java CompletableFuture can be created by the following methods:
Creating an empty Java CompletableFuture
Create an empty CompletableFuture and add stages to it. It is incomplete and can be completed.
CompletableFuturecompletableFuture = new CompletableFuture<>();
Create an async Java CompletableFuture using a Supplier.
Create a completableFuture Using a Supplier<U>. The supplier provides the return value and the CompletableFuture is asynchronously completed.
CompletableFuturecompletableFutureSupplyAsync = CompletableFuture.supplyAsync(() -> { return Thread.currentThread().getName(); }); System.out.println(completableFutureSupplyAsync.get());// Prints ForkJoinPool.commonPool-worker-1
By Default the async process uses the ForkJoinPool, however we can pass our own Executor framework.
Executor executor = Executors.newCachedThreadPool(); CompletableFuturecompletetableFutureSupplyAsyncWithCustomExecutor = CompletableFuture .supplyAsync(() -> { return Thread.currentThread().getName(); }, executor); System.out.println(completetableFutureSupplyAsyncWithCustomExecutor.get());// prints pool-1-thread-1
Create an async Java CompletableFuture Using a Runnable.
If you don’t need to return a value, use the runAsync method instead. It takes in a Runnable
CompletableFuturecompletetableFutureRunAsync = CompletableFuture.runAsync(() -> { System.out.println(Thread.currentThread().getName()); });
Ways to complete a Java CompletableFuture.
CompletableFuture allows you to complete a task explicitly from the calling thread. It is possible to complete a task cleanly or to complete a task to through an Exception. Let’s look at some of the ways to complete.
complete a Java CompletableFuture without exception
Lets first see how to cleanly complete a CompletableFuture. In the example below we first create a CompletableFuture using the supplyAsync method and we put a sleep of 10 seconds inside the async task. We then complete the future externally. Since there is a sleep of 10s the internal task would not have a chance to complete and it will never return the value of “finished”. A ‘get()’ call simply returns the value that we pass to the complete() function and isDone() returns true.
CompletableFuturecompletableFuture = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(10000); } catch (InterruptedException e) { System.out.println("Interrupted"); } return "finished"; }); // complete it completableFuture.complete("Explicitly Finished"); try { System.out.println(completableFuture.get());// prints "Explicitly Finished" } catch (InterruptedException | ExecutionException e) { // no exception thrown e.printStackTrace(); } System.out.println(completableFuture.isDone());// prints true
complete a Java CompletableFuture with exception
In the previous example, we completed the CompletableFuture so that it just returns without throwing an exception, in this example we tell the async task to complete by throwing an Exception.
CompletableFuturecompletableFutureException = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(10000); } catch (InterruptedException e) { System.out.println("Interrupted"); } return "finished"; }); completableFutureException.completeExceptionally(new Exception("Explicitly Completed")); try { System.out.println(completableFutureException.get()); } catch (InterruptedException | ExecutionException e) { System.out.println(e.getMessage()); // prints java.lang.Exception: Explicitly Completed } System.out.println(completableFutureException.isCancelled());// prints false System.out.println(completableFutureException.isCompletedExceptionally());// prints true
CompletionStage methods of Java CompletableFuture – Chaining Tasks
CompletableFuture implements the CompletionStage interface and this interface provides a group of methods to arrange CompletableFuture Tasks. Lets look at some of the methods
Perform an action when one of two CompletableFuture completes
If there are two CompletableFutures and we need to perform an action with the first action that completes, then use acceptEither. Has an async variation.
CompletableFuturecompletableFuture = CompletableFuture.supplyAsync(() -> { return "Process 1"; }); CompletableFuture otherCompletableFuture = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } return "Process 2"; }); completableFuture.acceptEither(otherCompletableFuture, (s) -> { System.out.println(s); // prints "Process 1" });
The applyToEither variation performs an operation on the result and returns the result of that operation.
System.out.println(completableFuture.applyToEither(otherCompletableFuture, (s) -> { return s.toLowerCase(); }).get()); // print "process 1"
Handle both the result and Exception from a CompletableFuture
The CompletableFuture may either complete without problems or throw and Exception. Use the ‘handle’ method to handle both cases together.
CompletableFuturestage1 = completableFuture.handle((s, e) -> { if (e != null) System.out.println(e.getMessage()); return s.toLowerCase(); }); System.out.println(stage1.get()); // prints process1
Perform a Task after two CompletableFuture finish
CompletableFuturecompletableFuture = CompletableFuture.runAsync(() -> { System.out.println("Process 1"); }); CompletableFuture otherCompletableFuture = CompletableFuture.runAsync(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Process 2"); }); // use the runAfterBoth method to execute a Runnable after execution of two CompletableFutures completableFuture.runAfterBoth(otherCompletableFuture, () -> { System.out.println("After process 1 and process 2"); }).get(); // prints "Process 1" followed by "Process 2" and then "After process 1 and process 2"
Perform a Task that accepts the results of two CompletableFutures
CompletableFuturecompletableFuture = CompletableFuture.supplyAsync(() -> { return "Process 1"; }); CompletableFuture otherCompletableFuture = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } return "Process 2"; }); // use the runAfterBoth method to execute a Runnable after execution of two // CompletableFutures completableFuture.thenAcceptBoth(otherCompletableFuture, (s1, s2) -> { System.out.println("result of 1 : " + s1); System.out.println("result of 2 : " + s2); System.out.println("After process 1 and process 2"); }).get(); /*- * prints * result of 1 : Process 1 result of 2 : Process 2 After process 1 and process 2 */
Return a CompletableFuture when any one of a group of CompletableFuture complete
CompletableFuturecompletableFuture = CompletableFuture.supplyAsync(() -> { return "Process 1"; }); CompletableFuture otherCompletableFuture = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } return "Process 2"; }); // returns a completablefuture when any one of the two completes System.out.println(CompletableFuture.anyOf(completableFuture, otherCompletableFuture).get());// prints Process 1
Return a CompletableFuture when All of a group of CompletableFuture complete
This method can be used to wait till all the asynchronous processes complete. No results are
returned
CompletableFuturecompletableFuture = CompletableFuture.supplyAsync(() -> { return "Process 1"; }); CompletableFuture otherCompletableFuture = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } return "Process 2"; }); System.out.println(CompletableFuture.allOf(completableFuture, otherCompletableFuture).get()); // prints null
We have described some of the methods of Java CompletableFuture. To look at the complete list, check out the CompletionStage docs.