This is a follow-up to Retry in Java futures. This post presented a snippet of code demonstrating how to compose futures in Java, specifically java.util.concurrent.CompletableFuture. It demonstrated how to swallow exceptions and compose futures including branching etc.
It turns out that I need to interoperate with Guava futures too, specifically com.google.common.util.concurrent.ListenableFuture. In some ways this post can be considered to be a short translation guide for switching back and forth between CompletableFuture and ListenableFuture.
On first encountering ListenableFuture I was dismayed that, unlike CompletableFuture, this interface does not expose any methods for composing futures. CompletableFuture, for example, provides the following and many more:
exceptionallythenComposethenApplyOn deeper inspection, however, we find that Guava eschews interface methods for static helper methods on the com.google.common.util.concurrent.Futures class. Roughly corresponding to the methods mentioned above we find:
catchingAsync corresponding to exceptionallytransformAsync corresponding to thenComposetransform corresponding to thenApplyMy “add-with-retry” example using CompletableFuture looks like:
These are my observations about this:
.exceptionally(e -> null)null returned from the App.add future and a swallowed exceptionexceptionally-followed-by-thenCompose hackRegarding the union type comment above, I can think of at least one more principled approach we might take to handling exceptions that does not lose information. We could, for example, introduce a tagged union type as follows:
With the introduction of the tagged union, ValueOrException (roughly equivalent to Haskell’s Either), we can now distinguish accurately between the exceptional case and the null response case.
Here’s my rough translation of this example into Guava futures:
And here’s the accompanying ListenableFutureHelper helper class:
Here are my thoughts:
Futures as opposed to first-class members of the ListenableFuture interfaceListenableFuture’s catchingAsync is far superior to exceptionally followed by thenCompose and does not lose information and does not require the invention of a tagged union to distinguish between null and exceptionsIn order to address the syntactic issues with ListenableFuture, Guava now has com.google.common.util.concurrent.FluentFuture in recent versions of the library. I haven’t had a chance to play with this yet. If I do, I’ll get back to you!
The latest and greatest version of this code can be found in the GitHub project.
Update: Here’s the FluentFuture version
That’s better!
Update 2: Cancellation
You’ll notice that I sneakily introduced early cancellation into the FluentFuture example. If you want to see how I added support for proper cancellation using all three future APIs, please consult the GitHub project.
Content © 2025 Richard Cook. All rights reserved.