A library that simplifies error handling for Functional Programming in Java
Faux pas noun, /fəʊ pɑː/: blunder; misstep, false step
Faux Pas is a library that simplifies error handling for Functional Programming in Java. It fixes the
issue that none of the functional interfaces in the Java Runtime by default is allowed to throw checked exceptions.
interface Client {
User read(final String name) throws IOException;
}
Function<String, User> readUser = throwingFunction(client::read);
readUser.apply("Bob"); // may throw IOException directly
Add the following dependency to your project:
<dependency>
<groupId>org.zalando</groupId>
<artifactId>faux-pas</artifactId>
<version>${faux-pas.version}</version>
</dependency>
Faux Pas has a variant of every major functional interface from the Java core:
ThrowingRunnable
ThrowingSupplier
ThrowingConsumer
ThrowingFunction
ThrowingUnaryOperator
ThrowingPredicate
ThrowingBiConsumer
ThrowingBiFunction
ThrowingBinaryOperator
ThrowingBiPredicate
The followings statements apply to each of them:
The way the Java runtime implemented functional interfaces always requires additional type information, either by
using a cast or a local variable:
// compiler error
client::read.apply(name);
// too verbose
((ThrowingFunction<String, User, IOException>) client::read).apply(name);
// local variable may not always be desired
ThrowingFunction<String, User, IOException> readUser = client::read;
readUser.apply(name);
As a workaround there is a static factory method for every interface type inFauxPas
. All of them are called
throwingRunnable
, throwingSupplier
and so forth. It allows for concise one-line statements:
List<User> users = names.stream()
.map(throwingFunction(client::read))
.collect(toList());
Traditional try-with-resources
statements are compiled into byte code that includes
unreachable parts and unfortunately JaCoCo has no
support for filtering yet. That’s why we came up with an
alternative implementation. The official example
for the try-with-resources
statement looks like this:
try (BufferedReader br =
new BufferedReader(new FileReader(path))) {
return br.readLine();
}
Compared to ours:
return tryWith(new BufferedReader(new FileReader(path)), br ->
br.readLine()
);
CompletableFuture.exceptionally(..)
is a very powerful but often overlooked tool. It allows to inject partial exception handling
into a CompletableFuture
:
future.exceptionally(e -> {
Throwable t = e instanceof CompletionException ? e.getCause() : e;
if (t instanceof NoRouteToHostException) {
return fallbackValueFor(e);
}
throw e instanceof CompletionException ? e : new CompletionException(t);
})
Unfortunately it has a contract that makes it harder to use than it needs to:
Throwable
as an argument, butCompletionException
beforeCompletionException
exceptionally(..)
callIn order to use the operation correctly one needs to follow these rules:
CompletionException
.CompletionException
before throwing.FauxPas.partially(..)
relives some of the pain by changing the interface and contract a bit to make it more usable.
The following example is functionally equivalent to the one from above:
future.exceptionally(partially(e -> {
if (e instanceof NoRouteToHostException) {
return fallbackValueFor(e);
}
throw e;
}))
ThrowingFunction<Throwable, T, Throwable>
, i.e. it allows clients to
CompletionException
before passing it to the given function.CompletionException
directly. Except for the rare occasionCompletionException
has no cause, in which case it will be passed to the given function.Exception
inside a CompletionException
, if needed.The last example is actually so common, that there is an overloaded version of partially
that caters for this use
particular case:
future.exceptionally(partially(NoRouteToHostException.class, this::fallbackValueFor))
future.whenComplete(failedWith(TimeoutException.class, e -> {
request.cancel();
}))
Other missing pieces in CompletableFuture
’s API are exceptionallyCompose
and handleCompose
. Both can be seen as
a combination of exceptionally
+ compose
and handle
+ compose
respectively. They basically allow to supply
another CompletableFuture
rather than concrete values directly. This is allows for asynchronous fallbacks:
exceptionallyCompose(users.find(name), e -> archive.find(name))
If you have questions, concerns, bug reports, etc., please file an issue in this repository’s Issue Tracker.
To contribute, simply make a pull request and add a brief description (1-2 sentences) of your addition or change. For
more details, check the contribution guidelines.