Skip to content
Home » Completablefuture Spring boot Java 8

Completablefuture Spring boot Java 8

  • by
Completablefuture Spring boot Java 8

1. Overview

In this article, we will discuss the CompletableFuture Java 8 feature along with examples in Spring boot. This CompletableFuture interface was added to the java.util.concurrent package in the Java 8 release.

2. Java 8 CompletableFuture

Future is an interface introduced in Java 5. A CompletableFuture introduced later in Java 8 implements the Future interface and also CompletionStage interface. So CompletableFuture contains all the functionalities provided by the Future interface.

The CompletionStage<T> is an interface of which CompletableFuture<T> is the only current implementing class.

2.1. Execute CompletableFuture

You can use the CompletableFuture interface for asynchronous programming. In other words, this interface runs the code as a task in a non-blocking thread. After execution, it notifies the caller thread about the task progress, completion, or any failure.

This CompletableFuture has the following methods to execute the code:

1. supplyAsync

This method takes a Supplier as an argument and returns the CompletableFuture<U> instance back to the caller thread. A supplier is nothing but a function that contains the code to be executed asynchronously. This function returns a value that you can retrieve later by using the CompletableFuture<U> instance.

CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
            System.out.println("SupplyAsync : " +
                    Thread.currentThread().getName());
            return "Success";
        });

2. RunAsync

This method accepts a Runnable as an input parameter and returns CompletableFuture<Void>. If you notice, the type of the CompletableFuture is void, meaning this method won’t return any result back to the caller.

CompletableFuture<Void> cf = CompletableFuture.runAsync(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
            System.out.println("runAsync executed in the thread: " +
                    Thread.currentThread().getName());
        });

2.2. Wait for CompletableFuture to complete

The main purpose of both the join and get methods is to wait for the task to complete.

If you are using supplyAsync method to run your non-blocking code, then the get or join method would wait for the task to complete and then fetches the result of the task. However, runAsync (Runnable) task will return nothing so there is no point in checking the result of the task. For runAsync tasks, you can use get() or join() only to wait for them to complete.

To understand the differences between the join and get method, refer to this article.

2.2.1. join method example

CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(20000);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
            System.out.println("supplyAsync executed in the thread: " +
                    Thread.currentThread().getName() + Thread.currentThread().isDaemon());
            return "SUCCESS";
        });
        System.out.println(cf.join());

2.2.2. get method example

CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
    try {
        Thread.sleep(20000);
    } catch (InterruptedException ex) {
        ex.printStackTrace();
    }
    System.out.println("supplyAsync executed in the thread: " +
       Thread.currentThread().getName());
    return "SUCCESS";
});
try {
    System.out.println(cf.get());
} catch (InterruptedException | ExecutionException | TimeoutException ex) {
    ex.printStackTrace();
}

2.3. Chain multiple tasks

The CompletableFuture also allows you to chain tasks together. Using this CompletableFutures, we can process the result of the task without actually blocking a thread to wait for the result.

Along with asynchronous programming, the CompletableFuture handles computation heavy MapReduce (parallel processing by splitting big data sets into smaller chunks) tasks, without worrying much about application performance.

For example, the following CompletableFuture chain multiple tasks together by using the callback methods like thenApply. After CompletableFuture code execution completes (supplyAsync), it passes the value 5 to the downstream thenApply call where we calculate the square. Again, passes the square number to the next downstream method and so on.

To learn the difference between thenApply and thenCompose, refer this article.

CompletableFuture<Integer> f = CompletableFuture.supplyAsync(() -> {
                System.out.println("Performing mathematical computation.. 5 ");
                return 5;
        }).thenApply(number -> {
            System.out.println("Result of completable future: " + number);
            return number * number;
        }).thenApply(square -> {
            System.out.println("Result of completable future: " + square);
            return square*2;
        }).whenComplete((doubleNumber, throwable) -> {
            System.out.println("Result of completable future: " + doubleNumber);
        });

2.4. Combine CompletableFutures together

The allOf method of the CompletableFuture accepts an array of CompletableFutures as an argument and returns a new CompletableFuture. Using the new CompletableFuture, you can wait for those provided CompletableFutures to complete normally or exceptionally.

You had to wait for this returned CompletableFuture to complete by using any of the callback methods such as get(), join()whenComplete() so on.

If any of the provided CompletableFutures complete exceptionally, then the returned CompletableFuture also throws a CompletionException having the aforesaid exception as its cause. Note that the returned CompletableFuture does not reflect the results of the provided CompletableFutures. However, you can get the result by inspecting each CompletableFuture individually.

Assume our project registers a new user after validating their details. So we have two CompletableFuture instances: one for validating the details and another for registering the new user. If the validation and registration are successful, then we want to update UI success or failure if any exceptions.

So we had to create a new CompletableFuture using allOf method which can wait for these two CompletableFuture instances to complete and return an exception if any.

CompletableFuture<Boolean> validateNewUser = CompletableFuture.supplyAsync(() -> {
     System.out.println("Validation of new user : SUCCESS");
     return true;
});


CompletableFuture<Boolean> registerNewUser = CompletableFuture.supplyAsync(() -> {
    System.out.println("Registration of new user : success");
    return true;
});

CompletableFuture<Void> finalCompletableFuture = CompletableFuture.allOf(validateNewUser,
                registerNewUser);

finalCompletableFuture.whenComplete((result, throwable) -> {
    System.out.println("Result : " + result);
    System.out.println("Throwable : " + throwable);
    if (throwable == null) {

    } else {

    }
});

3. Conclusion

To sum up, we have learned the CompletableFuture, a Java 8 feature with Spring boot examples.

Leave a Reply

Your email address will not be published. Required fields are marked *