Looking for an Expert Development Team? Take two weeks Trial! Try Now or Call: +91.9824127020

CompletableFuture in Java

Technology

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

It helps improve application performance, as it executes separately from the main thread.

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 the task, and the get() method is used to retrieve the output/ return value of the execution.

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

Limitations of Future API:

1. Manual Completion method

Suppose 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 the main execution thread

Future will not notify the main execution thread after it completes the execution. It provides a get() method, which will block the main thread until it completes and provides a response. The Future API does not provide any callback methods that will be called on the success/failure of a task completion condition.

3. Multiple Futures can not chain together:

Sometimes a task may depend on another task’s result, and in these cases, we need to associate these tasks based on the completion status of the previous task. This is particularly important for IT companies, as project workflows often hinge on the timely execution and completion of interdependent tasks. However, these dependencies are not provided in the future API.

4. Combining Multiple Future

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 the Future and CompletionStage interface.

CompletionStage interface

A stage of a possibly asynchronous computation, that performs an action or computes a value when another CompletionStage completes. A phase completes at the end of its computation, but this in turn can trigger other dependent phases. The functionality defined in this interface takes only a few basic forms, which expand 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 mark the task as complete with a specified stringlater we are invoking the get() method to get a return of the task. Any subsequent calls to the future object will be ignored because the function was completed.

2. Asynchronously running Tasks 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 returns the void type of CompletableFuture.

3. If the CompletableFuture needs to inform the main thread with some return value then we can use the 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:

thenApply(): thenApply() is the method useful to execute some function after returning the completableFuture. It’s like a callback for CompletableFuture, this callback function will be executed by the main thread after CompletableFuture returns the value to the 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());

thenApplyAsync(): This method will do the same as the thenApply() method, but the difference is callback will be executed in a separate thread, whereas thenApply() will be executed by the 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 the value of the previous thenApply() method value is input next thenApply() method and it goes.

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

The difference between these is thenAccept() will take value from the 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.

Chaining CompletableFutures: Let’s say that we have 2 applications, one provides the user information, other provides the user’s credit card information. If we want to fetch the user’s credit card information parallelly, then we can use the 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 the thenCompose() method for returning CompletableFuture of Object type.

CompletableFuture<Double>  result = getUserDetail(userId).thenCompose(user ->getCreditRating(user));

thenCombine(): we can combine two independent CompletableFutures with the 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());

applyToEither(): Let us use this method if we want to execute some method for the first result between two CompletableFuture.

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

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 the handle method, which takes two parameters, success value and exception. If the exception contains value means a task-thrown exception.

Conclusion

We explored the most commonly used and important concepts of CompletableFuture introduced in Java, and we also learned the differences between Future and CompletableFuture.

Aegis Infoways

Aegis Infoways is a leading software development company that provides a wide range of business solutions like software development, data warehouse, or web development for specific business needs.

Related Posts

10 Top B2B E-commerce Websites to Find Buyers...

10 Top B2B E-commerce Websites to Find Buyers...

Businesses must have B2B eCommerce platforms to expand operations to other nations, build relationships with dependable suppliers, and source goods more efficiently. As we approach 2025, these platforms will facilitate seamless global trade, whether for small...

10 Eclipse Java Plug-ins You Can’t Do Witho...

10 Eclipse Java Plug-ins You Can’t Do Witho...

Eclipse is the most widely used integrated development environment for Java. Used to develop the Java applications, Eclipse is also often used to develop applications. Its extensive plug-ins give it the flexibility to be customized. This open-source software has...

Dynamic Property Source in Spring 5?

Dynamic Property Source in Spring 5?

Technology Spring framework is the most used Java framework for developing all types of applications. In the recent version of Spring( aka Spring 5), the spring-test artifact introduced the new annotation @DynamicProperySource, which will be used to resolve the...

×