CompletableFuture in Java

Introduction to CompletableFuture

CompletableFuture was one of the feature released in Java 8. CompletableFuture implements the CompletionStage and Future interfaces. It is mainly used for asynchronous programming in Java.

Asynchronous programming is a approach of writing non-blocking code by running a task on separate threads rather than main application’s thread and notifying the main thread about the progress status.

This approach will not lead main thread to be blocked for any task completion and it can execute multiple other tasks parallely.

This parallel working approach definitely improves the performance.

Hierarchy of CompletableFuture

CompletableFuture

Future vs CompletableFuture

CompletableFuture can be considered as an extension to Future API which was introduced in Java version 5.

Future provides two methods –  isDone() and get() method. isDone() method checks whether the computation is completed or not and get() method retrieves the result of the computation when it is done. Thus, A Future can be used as a reference to the result of an asynchronous computation.

Future API definitely lacks some important features :

Limitations of Future

Manual completion is not possible 

Completion of Future manually is not possible. Let us understand this with example , suppose we have one function which fetches data from a remote API. Data here is the latest price of the product. As API calls are time consuming , we are running it in separate threads.

In case , the remote service is down and now you required to complete the Future manually with the cached data. It will not be possible with the Future.

Performing further actions on a Future’s result is not possible without blocking

One of the major limitation of Future is that it never notifies the completion of execution. Future API has a get() method and this method blocks the code  until the result is not returned by Future.

We can not attach any callback function with the Future to make it automatically called when the Future’s result is returned.

Chaining of multiple Futures is not possible

With future, we can not create asynchronous tasks to be performed.

For Example : If you want to perform one task and another task after completion of first task and so on. This is not possible with Future.

Multiple Futures cannot be combined together

Suppose, we need to perform n-number of different tasks parallely with n-number of different Futures. This is not possible with this API.

Lack of Exception Handling

One major limitation of Future API that it lacks the exception handling construct.

How to create CompletableFuture

In below examples, we will learn :

  • How to create CompletableFuture
  • Uses of runAsync()method
  • Uses of supplyAsync()method

1. Basic example to create CompletableFuture

The simple CompletableFuture can be created using its no-arg constructor :

CompletableFuture<String> completableFuture = new CompletableFuture<String>();

To get the result of this CompletableFuture , simple get() method needs to be called :

String output = completableFuture.get()

As we know, The get() method blocks the code until the Future gets completed and return result. So, the above execution will block forever as the Future is never completed.

So we can use complete() method of CompletableFuture to manually complete a Future –

completableFuture.complete("The result of Future")

All the threads which are waiting for this Future will get the result also all the subsequent calls to completableFuture.complete() will get ignored.

2. Uses of runAsync()to run asynchronous computation

CompletableFuture’s runAsync()methods can be used if you want to run background tasks asynchronously and do not want any result returned from the task,  This method takes a Runnable object as argument and returns CompletableFuture<Void>.

// Running the task asynchronously.
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(new Runnable() {
    @Override
    public void run() {
        // Below is the task
        try {
            TimeUnit.SECONDS.sleep(1);
        } 
        catch (InterruptedException e) {
            throw new IllegalStateException(e);
        }

        System.out.println("Separate thread");
    }
});

// Block the code and wait for the completableFuture to complete
completableFuture.get()

To reduce the lines of code, we can also use lambda expression :

// Running the task asynchronously.
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync( () -> {
        // Below is the task
        try {
            TimeUnit.SECONDS.sleep(1);
        } 
        catch (InterruptedException e) {
            throw new IllegalStateException(e);
        }

        System.out.println("Separate thread");
});

3. Uses of supplyAsync()to run a task asynchronously and also return the result

runAsync()method of CompletableFuture works great when computations needs to be performed asynchronously but it does not return result. If you want asynchronous computation but also want result then supplyAsync() method is used.

This method takes a Supplier<T> as argument and returns CompletableFuture<T> .

T denotes the type of the value obtained by calling the given supplier –

// Running a task specified by a Supplier object asynchronously
CompletableFuture<String> completableFuture  = CompletableFuture.supplyAsync(new Supplier<String>() {
    @Override
    public String get() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new IllegalStateException(e);
        }
        return "Result";
    }
});

// Block the code and get the result
String output = completableFuture.get();
System.out.println(output);

Supplier<T> is a functional interface. It represents a supplier of results. It has one method get() where you can write your asynchronous background task and return the result.

To reduce the lines of code, we can also use lambda expression :

// With Lambda Expression
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        throw new IllegalStateException(e);
    }
    return "Result";
});

Conclusion

That’s all folks! In this article, you have got the complete overview of CompletableFuture in Java.

Newsletter Updates

Enter your name and email address below to subscribe to our newsletter

Leave a Reply