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.

What is CompletableFuture?

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.

What is the difference between Future and CompletableFuture in Java?

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 VS CompletableFuture API

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.

CompletableFuture API

Future API

It can be completed manually. While using a remote-API, if the remote API service gets down, you can complete the Future API manually to retrieve the data.

 

Future API does not allow manual completion. The program would come to a halt and stop responding if a remote API server gets down.
It allows a callback function to the API and has it called automatically when the result is available. This way, further actions can be performed on a received result from the API. Future does not notify of the completion on an API. It just provides a get() method which blocks until the result is available to the main thread. Ultimately, it restricts users from applying any further action on the result.
You can create an asynchronous workflow with CompletableFuture. It allows chaining multiple APIs, sending ones to result to another.

 

Future API does not allow chaining multiple future APIs. It needs to be done manually.
It allows combining multiple futures in CompletableFuture. You can now run multiple APIs in parallel and then it can also be combined with some functions afterward when all of them completes. Combining multiple futures is possible in Future APIs. You also have to run the functions afterward by yourself if you wish to combine them.
It also offers exception handling. CompletableFuture, it implements Future and CompletionStage interfaces to provides a huge set of all convenient methods along with a very comprehensive exception handling support.

 

Future API did not have any exception handling construct.

Let’s create a CompletableFuture

Starting with the basics.

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.

Running an asynchronous computation using runAsync() method

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.

Transforming and acting on a CompletableFuture

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()

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

· thenAccept()

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())
});

· thenRun()

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.
});

Combining two CompletableFutures

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

Conclusion

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.

Author

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.

Write A Comment