Java 8 introduced a lot of new features like Streams API and Lambda. One of the prominent additions was CompletableFuture. This article will be discussing CompletableFutureJava and all its methods. We will look at how it works and how you can use it in your Java code using simple examples.
Table of Contents
CompletableFuture is a feature for asynchronous programming using Java. Unlike procedural programming, asynchronous programming is about writing a non-blocking code by running all the tasks on separate threads instead of the main application thread and keep notifying the main thread about the progress, completion status, or if the task fails.
This way, your main thread does not get blocked while waiting for the completion of a task as it can be executed in parallel. This kind of parallelism can significantly improve the performance of your programs and the time taken for execution.
You would have probably heard about Future API in Java. CompletableFuture is an extension to the Future API which was introduced way back in Java 5. Future API is used as a reference to the results of asynchronous computations from CompletableFutureJava. It provides an isDone() method to confirm whether the computation is done or not, and a get() method to retrieve the result of the computation after it is done.
Future API was a great step taken towards asynchronous programming in Java but it lacked some very important and crucial features. That is where CompletableFuture comes in as it offers all those features that were limited in Future API.
You can create a CompletableFuture by simply using the following non-argument constructor
CompletableFuture<String> completableFuture = new CompletableFuture<String>();
This is the most basic CompletableFuture that you can make.
All the clients who want to get the result of this CompletableFuture can call CompletableFuture.get()method like this,
String result = completableFuture.get()
As mentioned before, the get() method blocks until the Future is completed. So, the above-mentioned call will be blocked forever if the Future will never be completed.
To cater to that, you can use CompletableFuture.complete() method to manually complete a Future,
completableFuture.complete("Result of Future")
All the clients waiting for this Future will receive the specified result and the subsequent calls to completableFuture.complete() will be ignored.
If you wish to run a background task asynchronously and do not want to return anything from that task, you can use CompletableFuture.runAsync() method. It takes a Runnable object and returns CompletableFuture<Void>.
// Run a task asynchronously, specified by a Runnable Object. CompletableFuture<Void> future = CompletableFuture.runAsync(new Runnable() { @Override public void run() { // Simulate a long-running Job try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { throw new IllegalStateException(e); } System.out.println("this will run in a separate thread from the main thread."); } }); // Block and wait for the future to complete future.get()
CompletableFuture.runAsync() is used for tasks that do not need to return anything but what if you want to return the result from your background task there is a method for it, CompletableFuture.supplyAsync().
It takes a Supplier<T> and will return CompletableFuture<T> where T is the type of the value obtained by calling the given supplier
// Run a task asynchronously, specified by a Supplier object. CompletableFuture<String> future = CompletableFuture.supplyAsync(new Supplier<String>() { @Override public String get() { try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { throw new IllegalStateException(e); } return "Result of the asynchronous computation"; } }); // Block and get the result of the Future String result = future.get(); System.out.println(result);
A Supplier<T> is a simple functional interface that represents a supplier of results. It has a single get() method where you can write your background task and return the result.
For building asynchronous systems, you need to attach a callback to the CompletableFuture which should automatically get called when the Future completes.
You can do that using the following methods,
thenApply() method is used to process and transform the result of a CompletableFuture as it arrives. It takes a Function<T,R> as an argument. Function<T,R> is a basic functional interface representing a function that accepts an argument of type T and returns a result of type R. See this example below,
// Creating a CompletableFuture CompletableFuture<String> FirstNameFuture = CompletableFuture.supplyAsync(() -> { try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { throw new IllegalStateException(e); } return "Shaharyar"; }); // Attaching a callback to the Future using thenApply() CompletableFuture<String> FirstNameFuture = LastNameFuture.thenApply(firstName -> { return firstName + " Lalani"; }); // Now, Block and get the result of the future. System.out.println(greetingFuture.get()); // Shaharyar Lalani
You can also write a sequence of transformations on the CompletableFuture by attaching a several thenApply() callback methods. The result of one thenApply() method will be passed to the next in the series,
CompletableFuture<String> Name = CompletableFuture.supplyAsync(() -> { try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { throw new IllegalStateException(e); } return "Shaharyar"; }).thenApply(firstName -> { return firstName + " Lalani"; }).thenApply(fullName -> { return fullname + ", is my Full Name"; }); System.out.println(Name.get()); // Prints, Shaharyar Lalani is my Full Name
Now, If you do not want to return anything from your callback function and just want to run a piece of code after the Future is executed, you can use thenAccept() and thenRun() methods. These methods are consumers and are used as the last callback in the callback chain.
CompletableFuture.thenAccept() takes a Consumer<T> as argument and returns CompletableFuture<Void>.
// thenAccept() demo CompletableFuture.supplyAsync(() -> { return EventBooking.getpricingDetail(eventId); }).thenAccept(event -> { System.out.println("Got the ticket prices of " + event.getName()) });
While thenAccept() has access to CompletableFuture results, thenRun() does not even have access to the results but it also takes a Runnable and returns CompletableFuture<Void>
// thenRun() demo CompletableFuture.supplyAsync(() -> { // Running the computation }).thenRun(() -> { // Computation done. });
If you want to fetch the data from a remote API service and from using that data, you want to fetch some related information from another service, you can do that using thenCompose()method.
Consider the following implementations of getEventDetail ()
and getTicketsAvailable() methods
CompletableFuture<User> getEventDetail(String eventId) { return CompletableFuture.supplyAsync(() -> { return EventBooking.getpricingDetail(eventId); }); } CompletableFuture<Double> getTicketsAvailable(Event event) { return CompletableFuture.supplyAsync(() -> { return EventTickets.getTicketsAvailable(event); }); }
Now, you can use thenCompose() to get the results from combined Futures
CompletableFuture<CompletableFuture<Double>> result = getEventDetail(eventId) .thenCompose(event -> getTicketsAvailable(event));
In earlier examples, we used thenApply() but in this case, it is returning a CompletableFuture. That is why, the final result in the above case is a nested Completable Future we have used thenCompose() method for the final result to be the top-level Future.
Similarly, if you want to combine two independent Futures you can use the thenCompose() method for that.
See Also: Understanding Memory Management In Java
In this article, we have explored some of the most basic yet very important concepts of CompletableFutureJava API. These examples were just for starters but you can further explore CompletableFuture and features like how to combine more than two Futures or how to handle exceptions in CompletableFuture.
Shaharyar Lalani is a developer with a strong interest in business analysis, project management, and UX design. He writes and teaches extensively on themes current in the world of web and app development, especially in Java technology.
Create a free profile and find your next great opportunity.
Sign up and find a perfect match for your team.
Xperti vets skilled professionals with its unique talent-matching process.
Connect and engage with technology enthusiasts.
© Xperti.io All Rights Reserved
Privacy
Terms of use