An Introduction To Java Future

In this article, you will learn about the concept of Future in Java. Also, we will see how completable future works.

Java Future represents an async work part, similar to async/await in C#. Once the async work part is created, it returns the reference to a Future. You can manipulate this async work using the Future reference.
 
Java Future has the following definition.
  1. public interface Future<V> {  
  2.     boolean cancel(boolean mayInterruptIfRunning)  
  3.     V       get();  
  4.     V       get(long timeout, TimeUnit unit);  
  5.     boolean isCancelled();  
  6.     boolean isDone();  
  7. }  
If you want to learn more about it, the Oracle documentation for Java Future can be found here
 
Let us now see an example.
 
I will use a simple example of calculating the total of (0 to 10) * 10.
 
If I do the implementation in Java without using any of the Futures, the following code will be it.
  1. public class SequentialWorker {  
  2.    public int Do(){  
  3.   
  4.        int total =0;  
  5.        for(int j=0; j<10; j++) {  
  6.            for (int i = 0; i < 10; i++) {  
  7.                total += i;  
  8.            }  
  9.        }  
  10.   
  11.        return total;  
  12.    }  
  13. }  
And, I call this a SequencialWorker since it does not use any threads (Futures).
 
Now, I’m going to divide this work into Futures and get the job done by using 10 Futures and finally, combining their value to get the final total.
  1. public class ParallelWorker {  
  2.   
  3.    private ExecutorService executorService = Executors.newFixedThreadPool(10);  
  4.    private List<Future<Integer>> futureList = new ArrayList<>();  
  5.   
  6.    public int Do(){  
  7.   
  8.        int total = 0;  
  9.   
  10.        // Divide in to multiple futures  
  11.        for(int i=0;i<10;i++){  
  12.   
  13.            Future<Integer> future = executorService.submit(()->{  
  14.                int t = 0;  
  15.                for(int j=0;j<10;j++){  
  16.                    t += j;  
  17.                }  
  18.                return t;  
  19.            });  
  20.            futureList.add(future);  
  21.        }  
  22.   
  23.        // Get the total from each future  
  24.        for(Future<Integer> f : futureList){  
  25.            try {  
  26.                total += f.get();  
  27.            } catch (InterruptedException e) {  
  28.                e.printStackTrace();  
  29.            } catch (ExecutionException e) {  
  30.                e.printStackTrace();  
  31.            }  
  32.        }  
  33.   
  34.        return total;  
  35.    }  
  36. }  
You will get the following output from running these two code blocks.
 
Java Futures
 

Limitations of Java Futures

  1. Future does not notify once its job is completed.

    (No callback- If you are an experienced C# programmer, you should have already used the BeginInvoke with callback attached to it, something similar to the following syntax.

    Callback(IAsyncResult ar)
    )


  2. You have to block the Future to get the results out; once you call the “get()” method in Java Future, it will be blocked.
  3. Can’t combine multiple Futures together.
  4. No exception handling is available.
  5. No way to manually complete the future.

How can we overcome these limitations?

 
The answer is Completable Futures.
 
Let’s take the same example and implement it using Completable Futures.
  1. public class Worker {    
  2.    public void Do() {  
  3.        Result result = new Result(0);  
  4.        for (int i = 0; i < 10; i++) {  
  5.            CompletableFuture<Integer> completableFuture = new CompletableFuture<>();  
  6.            completableFuture.supplyAsync(() -> {  
  7.                int t = 0;  
  8.                for (int j = 0; j < 10; j++) {  
  9.                    t += j;                     
  10.                }  
  11.                return t;  
  12.            }).thenApply(t -> {  
  13.                System.out.println("Total with Completablefuture  is : " + t);  
  14.                result.setValue(result.getValue() + t);  
  15.                return t;  
  16.            }).exceptionally(ex -> {  
  17.                System.out.println("Exception occur(ed) " + ex);  
  18.                return -1;  
  19.            }).join();  
  20.        }  
  21.   
  22.        System.out.println("Result with Completable futures : " + result.getValue());  
  23.   
  24.    }  
  25. }  
As you can see, I have used Completable Future and instead of calling the “get()” method of Future, I call using “thenApply” method to transform the result of the completablefuture with other (this basically uses Function<T,R>  that is a simple functional interface representing a function that accepts an argument of type T and produces a result of type R).
 
This will generate the following output:
 
Java Futures
 
(45 is total per one Future and 450 is the final outcome)
 
Let’s go into the features of completablefuture one by one. Previously, I mentioned the limitations we have with Futures. In the completablefuture implementation, here is how we have overcome those limitations.
  1. First, the Future notifies once the 1 to 10 total is collected, then we apply the next set of work, i.e., print the value and sum to the grand total.
  2. We do not need to call the “get()” method since it gets notified once completed (We can call the get(); in that case, the outcome will be the same as Future).
  3. I could not cover the combine operation with this sample itself, I will do a separate section before the end of this article… keep reading...
  4. You can add the “exceptionally” block to handle the exception
  5. Manually completion is straightforward, you can provide the final result and call the complete to immediately end the completablefuture like this.
    1. CompletableFuture<Integer> completableFuture = new CompletableFuture<>();  
    2. completableFuture.complete(-1);  
Now the interesting part.
 

How can we combine the completable futures?

 
Look at the following sample code.
  1. public class CombineOperation {  
  2.   
  3.    private class Person{  
  4.        int age;  
  5.   
  6.        public int getAge() {  
  7.            return age;  
  8.        }  
  9.   
  10.        public void setAge(int age) {  
  11.            this.age = age;  
  12.        }  
  13.    }  
  14.   
  15.    CompletableFuture<Person> getPerson() {  
  16.        return CompletableFuture.supplyAsync(() ->  new Person());  
  17.    }  
  18.   
  19.    CompletableFuture<Person> setAge(Person person, int age) {  
  20.        return CompletableFuture.supplyAsync(() -> {  
  21.            person.setAge(age);  
  22.            return person;  
  23.        });  
  24.    }  
  25.   
  26.    CompletableFuture<Person> print(Person p){  
  27.        return CompletableFuture.supplyAsync(()->{  
  28.            System.out.println(p.getAge());  
  29.            return p;  
  30.        });  
  31.    }  
  32.   
  33. }  
Here, I used three completablefutures, for the following operations.
  1. Get Person
  2. Set age of the person
  3. Print the person's age
Each of these operations is added as completablefuture.
 
And I can combine those together like this.
  1. getPerson()  
  2.                .thenCompose(p -> setAge(p, 25))  
  3.                .thenApply(p1 -> print(p1));  
If you have used Spring Webflux (Web on Reactive Stack), this is the underlying concept been used for many operations.
 
It is similar to what we use delegates in C# for combining the Lamba operations other than C# Lambda is single threaded (means they run on the same thread they have been created).
 
Please let me know any questions you may have!
 
Cheers!!