1. Overview
ListenableFuture and CompletableFuture are built on top of Java’s Future interface. The former is part of Google’s Guava library, whereas the latter is part of Java 8.
As we know, the Future interface doesn’t provide callback functionality. ListenableFuture and CompletableFuture both fill this gap. In this tutorial, we’ll learn callback mechanisms using both of them.
2. Callback in Async Task
Let’s define a use case where we need to download image files from a remote server and persist the names of the image files in a database. Since this task consists of operations over the network and consumes time, it’s a perfect case of using Java async capability.
We can create a function that downloads files from a remote server and attaches a listener that then pushes data to a database when the download completes.
Let’s see how to achieve this task using both ListenableFuture and CompletableFuture.
3. Callback in ListenableFuture
Let’s start by adding Google’s Guava library dependency in the pom.xml:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
</dependency>
Now, let’s mimic a Future that downloads files from a remote web server:
ExecutorService executorService = Executors.newFixedThreadPool(1);
ListeningExecutorService pool = MoreExecutors.listeningDecorator(executorService);
ListenableFuture<String> listenableFuture = pool.submit(downloadFile());
private static Callable<String> downloadFile() {
return () -> {
// Mimicking the downloading of a file by adding a sleep call
Thread.sleep(5000);
return "pic.jpg";
};
}
The above code creates an ExecutorService wrapped inside MoreExecutors to create a thread pool. Inside the submit method of ListenableFutureService, we pass a Callable<String> that downloads a file and returns the name of the file that returns a ListenableFuture.
To attach a callback on the ListenableFuture instance, Guava provides a utility method in Future:
Futures.addCallback(
listenableFuture,
new FutureCallback<>() {
@Override
public void onSuccess(String fileName) {
// code to push fileName to DB
}
@Override
public void onFailure(Throwable throwable) {
// code to take appropriate action when there is an error
}
},
executorService);
}
So, in this callback, both success and exception scenarios are handled. This way of using a callback is quite natural.
We can also add a listener by adding it directly to the ListenableFuture:
listenableFuture.addListener(
new Runnable() {
@Override
public void run() {
// logic to download file
}
},
executorService
);
This callback doesn’t have access to the result of Future as its input is Runnable. This callback method executes whether Future completes successfully or not.
After going through the callbacks in ListenableFuture, the next section explores the ways in CompletableFuture to achieve the same.
4. Callback in CompletableFuture
In CompletableFuture, there are many ways to attach a callback. The most popular ways are by using chaining methods such as thenApply(), thenAccept(), thenCompose(), exceptionally(), etc., that execute normally or exceptionally.
In this section, we’ll learn about a method whenComplete(). The best thing about this method is that it can be completed from any thread that wants it to be completed. Using the above file downloading example, let’s see how to use whenComplete():
CompletableFuture<String> completableFuture = new CompletableFuture<>();
Runnable runnable = downloadFile(completableFuture);
completableFuture.whenComplete(
(res, error) -> {
if (error != null) {
// handle the exception scenario
} else if (res != null) {
// send data to DB
}
});
new Thread(runnable).start();
private static Runnable downloadFile(CompletableFuture<String> completableFuture) {
return () -> {
try {
// logic to download to file;
} catch (InterruptedException e) {
log.error("exception is {} "+e);
}
completableFuture.complete("pic.jpg");
};
}
When downloading the file completes, the whenComplete() method executes either the success or failure conditions.
5. Conclusion
In this article, we learned about the callback mechanism in ListenableFuture and CompletableFuture.
We saw that ListenableFuture is a more natural and fluid callback API when compared to CompletableFuture.
We’re free to choose what fits best to our use case as CompletableFuture is part of core Java, and ListenableFuture is part of the very popular Guava library.
All the code examples used in this article are available over on GitHub.