Future vs CompletableFuture in Java- #2

Reading Time: 3 minutes

In our previous blog – Future vs CompletableFuture – #1, we compared Java 5’s Future with Java 8’s CompletableFuture on the basis of two categories i.e. manual completion and attaching a callable method. Now, we will be comparing them on the basis of next 3 categories i.e.

  • Combining 2 CompletableFutures together
  • Combining multiple CompletableFutures together
  • Exception Handling

Let’s have a look at each one of them.

3. Combining 2 CompletableFutures together

In case of Future, there is no way to create asynchronous workflow i.e. long running computation. But CompletableFuture provides us with 2 methods to achieve this functionality:

i) thenCompose()

It is a method of combining 2 dependent futures together. This method takes a function that returns a CompletableFuture instance. The argument of this function is the result of the previous computation step. This allows us to use this value inside the next CompletableFuture‘s lambda.

For example:

private static void thenCompose() {
CompletableFuture<String> completableFuture =
CompletableFuture.supplyAsync(() > "Hello")
.thenCompose(value >
CompletableFuture.supplyAsync(
() > value + " Knolders! Its thenCompose"));
completableFuture.thenAccept(System.out::println); // Hello Knolders! Its thenCompose
}

view raw
thenCompose.java
hosted with ❤ by GitHub

Let’s compare this with the supplier method, thenApply() :

private static void thenApply() {
CompletableFuture<CompletableFuture<String>> completableFuture =
CompletableFuture.supplyAsync(() > "Hello")
.thenApply(value > CompletableFuture.supplyAsync(
() > value + " Knolders! Its thenApply"));
//Perform operation
}

view raw
thenApply.java
hosted with ❤ by GitHub

As you can see, the thenCompose() method is returning a value of type CompletableFuture whereas thenApply() is returning the value of type CompletableFuture in the same scenario.

Note: The thenCompose method together with thenApply implement basic building blocks of the monadic pattern. They closely relate to the map and flatMap methods of Stream and Optional classes also available in Java 8.

ii) thenCombine()

It is a method of combining 2 independent futures together and do something  with there result after both of them are complete. Combining is accomplished by taking 2 successful CompletionStages and having the results from both used as parameters to a BiFunction to produce another result.  For example:

private static void thenCombine() {
CompletableFuture<String> completableFuture
= CompletableFuture.supplyAsync(() > "Hello")
.thenCombine(CompletableFuture.supplyAsync(
() > " Knolders! Its thenCombine"), (value1, value2) > value1 + value2);
completableFuture.thenAccept(System.out::println); // Hello Knolders! Its thenCombine
}

view raw
thenCombine.java
hosted with ❤ by GitHub

iii) thenAcceptBoth()

It is used when you want to perform some operation with two independent Future’s result but don’t need to pass any resulting value down a Future chain. For example:

private static void thenAcceptBoth() {
CompletableFuture<Void> completableFuture = CompletableFuture.supplyAsync(() > "Hello")
.thenAcceptBoth(CompletableFuture.supplyAsync(() > " Knolders! Its thenAcceptBoth"),
(value1, value2) > System.out.println(value1 + value2)); // Hello Knolders! Its thenAcceptBoth
}

view raw
thenAcceptBoth.java
hosted with ❤ by GitHub

4. Combining multiple CompletableFutures together

What if there comes a scenario where you want to combine 100 different Futures that you want to run in parallel and then run some function after all of them completes. Future does not provide us any way in order to achieve this functionality but CompletableFuture does. There are two methods of implementing this:

i) CompletableFuture.allOf()

CompletableFuture.allOf()  static method is used in scenarios when you have a List of independent futures that you want to run in parallel and do something after all of them are complete.

private static void allOf() {
CompletableFuture<String> completableFuture1
= CompletableFuture.supplyAsync(() > "Hello");
CompletableFuture<String> completableFuture2
= CompletableFuture.supplyAsync(() > "Knolders!");
CompletableFuture<String> completableFuture3
= CompletableFuture.supplyAsync(() > "Its allOf");
CompletableFuture<Void> combinedFuture
= CompletableFuture.allOf(completableFuture1, completableFuture2, completableFuture3);
}

view raw
allOf.java
hosted with ❤ by GitHub

Limitation:

The limitation of this method is that the return type is CompletableFuture i.e. it does not return the combined results of all Futures. Instead you have to manually get results from Futures.

combinedFuture.get();
assertTrue(future1.isDone());
assertTrue(future2.isDone());
assertTrue(future3.isDone());

view raw
get.java
hosted with ❤ by GitHub

Fortunately, CompletableFuture.join() method and Java 8 Streams API helps to resolve this issue:

combinedFuture.thenApply(v ->
Stream.of(completableFuture1, completableFuture2, completableFuture3).
map(CompletableFuture::join).
collect(Collectors.toList()));
String combined = Stream.of(completableFuture1, completableFuture2, completableFuture3)
.map(CompletableFuture::join)
.collect(Collectors.joining(" "));
System.out.println(combined); // Hello Knolders! Its allOf

view raw
gistfile1.txt
hosted with ❤ by GitHub

Note : 

The CompletableFuture.join() method is similar to the CompletableFuture.get() method, but it throws an unchecked exception in case the Future does not complete normally. This makes it possible to use it as a method reference in the Stream.map() method.

ii) CompletableFuture.anyOf()

CompletableFuture.anyOf() as the name suggests, returns a new CompletableFuture which is completed when any of the given CompletableFutures complete, with the same result. CompletableFuture.anyOf() takes a varargs of Futures and returns CompletableFuture.

private static void anyOf() throws Exception {
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() > {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return "Result of Future 1";
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() > {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return "Result of Future 2";
});
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() > {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return "Result of Future 3";
});
CompletableFuture<Object> anyOfFuture = CompletableFuture.anyOf(future1, future2, future3);
anyOfFuture.thenAccept(System.out::println); // Result of Future 2
}

view raw
allOf.java
hosted with ❤ by GitHub

Limitation:The problem with CompletableFuture.anyOf() is that if you have CompletableFuture that return results of different types, then you won’t know the type of your final CompletableFuture.

5. Exception Handling

Let’s first understand how errors are propagated in a callback chain. Consider the following CompletableFuture callback chain –

private static void chainOfThenApply() {
CompletableFuture.supplyAsync(() > {
// Code which might throw an exception
return "Some result";
}).thenApply(result > {
return "processed result";
}).thenApply(result > {
return "result after further processing";
}).thenAccept(result > {
// do something with the final result
});
}

view raw
chain.java
hosted with ❤ by GitHub

If an error occurs in the original supplyAsync() task, then none of the thenApply() callbacks will be called and future will be resolved with the exception occurred. If an error occurs in first thenApply() callback then 2nd and 3rd callbacks won’t be called and the future will be resolved with the exception occurred, and so on.

So, there are 2 ways in order to handle this scenario:

i) Handle exceptions using exceptionally() callback

exceptionally() gives us a chance to recover by returning a default value or taking an alternative function that will be executed if preceding calculation fails with an exception.

private static void exception() {
Integer age = 1;
CompletableFuture<String> exceptionFuture = 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!";
});
exceptionFuture.thenAccept(System.out::println); //Unknown!
}

view raw
exceptionally.java
hosted with ❤ by GitHub

ii) Handle exceptions using the generic handle() method

The API also provides a more generic method – handle() to recover from exceptions. It is called whether or not an exception occurs. If an exception occurs, then the result argument will be null, otherwise, the ex argument will be null.

private static void exceptionUsingHandle() {
Integer age = 1;
CompletableFuture<String> exceptionFuture = CompletableFuture.supplyAsync(() > {
if (age < 0) {
throw new IllegalArgumentException("Age can not be negative");
}
if (age > 18) {
return "Adult";
} else {
return "Child";
}
}).handle((result, ex) > {
if (ex != null) {
System.out.println("Oops! We have an exception – " + ex.getMessage());
return "Unknown!";
}
return result;
});
exceptionFuture.thenAccept(System.out::println); // Unknown!
}

Here is the link for the demo code and link for my previous blog.

References :

knoldus-advt-sticker

Written by 

Jasmine is a Software Consultant, with an experience of more than 11 months. She is familiar with Object Oriented Programming Paradigms and has a good command over C/C++. Apart from being a programmer, She is also fond of relational database technologies such as PostGreSQL & MySQL. Apart from that, work life balance is one of the most important thing for her. Her hobbies include watching netflix,chatting ,surfing net and writing.

1 thought on “Future vs CompletableFuture in Java- #24 min read

  1. Java 8 has some exciting features at the JVM level and language level. Absolute must to know features of Java 8 are :
    1. Lambda Expressions
    2. Parallel Operations
    3. Introduction of Nashorn
    4. New Date/Time API’s
    5. Concurrent Accumulators

Comments are closed.

%d bloggers like this: