WE ARE TECHNOLOGY, WE ARE INNOVATION, WE ARE GLOBAL, WE ARE GROWTH

Java 8 CompletableFuture

Technology: CompletableFuture is the new feature added in java 8, which is an extension of Java Future API which was introduced in Java5.

CompletableFuture is used for asynchronous programming in Java. Asynchronous Programming means running task in a separate thread, other than main thread, and notifying the execution progress like completion or failure.

It helps in improving the performance of the application, as it is executing separately from main thread.

Quick Inquiry

captcha

Comparison between Future and CompletableFuture:

CompletableFuture is an extension to Java Future API. Future is the return type of the method of an asynchronous execution. Future provides isDone()to check the completion status of task, and get() method is used to retrieve the output/ return value of the execution.

Future is the first implementation to support the Asynchronous Programming in Java, and it comes with some limitations and some useful features.

Limitations of Future API:

1. Manual Completion method:
suppose if the requested some Rest API, and it is taking too long time to receive the message. We can not set the cached response and mark the task is done.

2. Notification to main execution thread
Future will not notify the main execution thread after it completes the execution. It provides get() method, which will block the main thread until it completes and provides the response. Future API doesn`t provide any callback methods which will be called upon success/failure of the task completion status.

3. Multiple Futures cannotchain together:
sometimes one task may depend on another task result, in these cases we need to chain these tasks based on previous task completion status, these are not provided in Future API.

4. Combining Multiple Future Together
let’s say we are executing 10 tasks in parallel using Future API, and we want to execute some piece of code once all these tasks are completed, this feature is not provided in Future.

5. Exception Handling:
Future API does not provide any exception handling.

All these limitations are implemented by extending the Future in CompletableFuture.This Class implements both Future, CompletionStage interface.

CompletionStage interface:
A stage of a possibly asynchronous computation, that performs an action or computes a value when another CompletionStage completes. A stage completes upon termination of its computation, but this may in turn trigger other dependent stages. The functionality defined in this interface takes only a few basic forms, which expand out to a larger set of methods to capture a range of usage styles.

All methods adhere to the triggering, execution, and exceptional completion specifications Additionally, while arguments used to pass a completion result (that is, for parameters of type T) for methods accepting them may be null, passing a null value for any other parameter will result in a NullPointerException being thrown.

1. Basic Example to create CompletableFuture:
CompletableFuture<String> future = new CompletableFuture<String>();
future.complete("result!!");
String result = future.get();
future is the object to execute the empty task, and we marking task as complete with specified string, later we are invoking get() method to get return of the task.
Any subsequent calls to future object will be ignored as the task was completed.

2. Running Tasks in asynchronous way using runAsync():

CompletableFuture<Void>completableFuture = CompletableFuture.runAsync(new Runnable() {                          
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("executing using runAsync() method");
}
});
completableFuture.get();
the same can be written as below using lambdas:
CompletableFuture<Void>completableFuture = CompletableFuture.runAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("executing using runAsync() method");
});
completableFuture.get();
runAsync() method takes Runnable implementation and return void type of CompletableFuture.

3. If the CompletableFuture needs to inform the main thread with some return value then we can use supplyAsync() method.
CompletableFuture<String>completableFuture = CompletableFuture.supplyAsync(() -> {
            try {
                        TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                        e.printStackTrace();
            }
            return "executing using supplyAsync() method";
});
String returnValue = completableFuture.get();
System.out.println(returnValue);

Chaining CompletableFutures :

  1. thenApply() : thenApply() is the method useful to execute some function after returning the completableFuture. Its like callback for CompletableFuture, this callback function will be executed by main thread after CompletableFuture return the value to main thread.

CompletableFuture<String>firstName = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "sravan";
});
CompletableFuture<String>fullName = firstName.thenApply(fName ->fName+" Kumar");
System.out.println(fullName.get());

  1. thenApplyAsync(): this method will do same like thenApply() method, but the difference is callback will be executed in separate thread, where as thenApply() will be executed by main thread.

CompletableFuture<String>firstName = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "sravan";
});
CompletableFuture<String>fullName = firstName.thenApplyAsync(fName ->fName+" Kumar");
System.out.println(fullName.get());
We can chain thenApply() methods to any number, return value of the previous thenApply() method value is input next thenApply() method and it goes..

  1. thenAccept(): if the callback functions don’t want to return anything then thenAccept() and thenRun() methods will be used.

The difference between these are thenAccept() will take value from previous execution, but thenRun() will not depend on the previous execution.
CompletableFuture<String>firstName = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "sravan";
});
CompletableFuture<Void>fullName = firstName.thenAccept(fName -> {
System.out.println(fName +" Kumar");
});
fullName.get();
same method signature is available for Async style also.

  1. Chaining CompletableFutures: Let`s say that we have 2 applications, for one provides the user information, other will provide the user credit card information. If we want to fetch the user credit card information in parallelly, then we can use thenApply() method, like:

CompletableFuture<User>getUsersDetail(String userId) {
return CompletableFuture.supplyAsync(() -> {
UserService.getUserDetails(userId);
});        
}

CompletableFuture<Double>getCreditRating(User user) {
return CompletableFuture.supplyAsync(() -> {
CreditRatingService.getCreditRating(user);
});
}
CompletableFuture<CompletableFuture<Double>> result = getUserDetail(userId).thenApply(user ->getCreditRating(user));
But the return type CompletableFuture of CompletableFuture. In these cases CompletabelFuture is providing thenCompose() method for returning CompletableFuture of Object type.
CompletableFuture<Double> result = getUserDetail(userId).thenCompose(user ->getCreditRating(user));

4. thenCombine(): we can combine two independent CompletableFutures with thenCombine() method.

CompletableFuture<String>firstName = CompletableFuture.supplyAsync(() -> {
            try {
                        TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                        e.printStackTrace();
            }
            return "Sravan";
});
CompletableFuture<String>lastName = CompletableFuture.supplyAsync(() -> {
            try {
                        TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                        e.printStackTrace();
            }
            return "Kumar";
});
CompletableFuture<String>fullName = firstName.thenCombine(lastName, (fName, lName) ->fName + " " + lName);
System.out.println("fullName - " + fullName.get());

5. applyToEither() : lets we want to execute the some method for first resulted between two CompletableFuture then we can use this method.
CompletableFuture<String>firstName = CompletableFuture.supplyAsync(() -> {
            try {
                        TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                        e.printStackTrace();
            }
            return "Sravan";
});
CompletableFuture<String>lastName = CompletableFuture.supplyAsync(() -> {
            try {
                        TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                        e.printStackTrace();
            }
            return "Kumar";
});
CompletableFuture<String>fullName = firstName.applyToEither(lastName, name -> name);
System.out.println("first returned  - " + fullName.get());

6. acceptEither(): it is same as applyToEither() but it will not return any value.
CompletableFuture<String>firstName = CompletableFuture.supplyAsync(() -> {
            try {
                        TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                        e.printStackTrace();
            }
            return "Sravan";
});
CompletableFuture<String>lastName = CompletableFuture.supplyAsync(() -> {
            try {
                        TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                        e.printStackTrace();
            }
            return "Kumar";
});
CompletableFuture<Void>fullName = firstName.acceptEither(lastName, name -> {
            System.out.println(name);
});

Exception Handling in CompletableFuture:

Similar thenApply() successful callback, we have one more callback for exception, exceptionally() method.

Integer age = -1;
CompletableFuture<String>maturityFuture = CompletableFuture.supplyAsync(() -> {
            if (age < 0) {
                        throw new IllegalArgumentException("Age can not be negative");
            }
            if (age > 18) {
                        return "Adult";
            } else {
                        return "Child";
            }
}).exceptionally(ex -> {
            System.out.println("Oops! We have an exception - " + ex.getMessage());
            return "Unknown!";
});
System.out.println("Maturity : " + maturityFuture.get());
We can also combine successful and error callbacks using handle method, it takes two parameters, success value and exception. If the exception contains value means task thrown exception.

Conclusion: we have explored most importantly used and important concepts of CompletableFuture introduced in Java 8, and also we leaned differences between Future and CompletableFuture.

Keep Visiting Aegis Infoways Blog for More information about Java Development Solutions.

Awesome clients we worked for

Client Testimonials

  • Fabio Durso

    We found a reliable and efficient partner in Aegis Infoways, ready to support our strategy of offshore software development. Aegis Infoways has particularly impressed by the speed of response to any request for the development of software applications and for their maintenance.

  • Filipe

    We did hire full time Java developers from Aegis Infoways, to help us to improve a time to market of a product. The software platform is based on Java & Extjs, and they are delivering the software on time and with success. We strongly recommend Aegis Infoways as Reliable Development partner.

  • Steve

    Powerful solutions are given by Aegis Infoways dedicated developers for my projects. They suggest solutions as per current market trend. Other than this, the team is always ready for any type of changes or update. That is the main reason that I would like to give my next project to them.

Copyright © 2016 - Aegis Infoways All rights reserved