A simple Result
type in Java for functional exception handling. Inspired by Haskell's Either
, antitypical/Result in Swift, and cyclops' Try
.
TL;DR; Result
allows you to call methods that throw checked exceptions in lambas without messy workarounds like nested try/catch or runtime exceptions.
With the advent of functional APIs in Java such as Optional
and Stream
, it's not uncommon to call methods which can throw a checked (or unchecked) exception in your lambdas. For instance, when parsing a URL from the app's environment:
Optional<URI> apiBaseURL = Optional.ofNullable(System.getenv("apiBaseURL")).map(baseUrlString -> {
// Compiler error, unhandled URISyntaxException
return new URI(baseUrlString);
});
In order to handle that URISyntaxException
and unpack the optional, you might wind up with code looking like this:
URI apiBaseURL;
try {
apiBaseURL = Optional
.ofNullable(System.getenv("apiBaseURL"))
.map(baseUrlString -> {
try {
return new URI(baseUrlString);
} catch (URISyntaxException e) {
// wrap as unchecked exception, catch outside of lambda
throw new RuntimeException("uri parsing error", e);
}
})
.get();
} catch (NoSuchElementException e) {
throw new IllegalStateException("Missing apiBaseURL env var");
} catch (RuntimeException e) {
// catch and re-throw the wrapped URISyntaxException
if (e.getCause() instanceof URISyntaxException) {
throw (URISyntaxException)e.getCause();
} else {
// something else weird happened, rethrow
throw e;
}
}
Variable reassignment, nested try/catch blocks, and runtime exceptions—oh my! Let's clean this up using Result
:
URI apiBaseURI = Result
.<String, Exception>attempt(Optional.ofNullable(System.getenv("apiBaseURL")::get) // 1
.flatMap(Result.from(URI::new)) // 2
.orElseThrow(); // 3
Here's the play by play:
attempt
toget
the optional value ofSystem.getenv(...)
, this yields aResult<String, Exception>
which either contains aString
or an exception (in this case, aNoSuchElementException
)flatMap
the initialResult
by trying to construct aURI
, which returns aResult<URI, Exception>
, containing either aURI
or an exception- Unpack the final
Result
, which will either throw one of the captured exceptions or return the transformed value
Other great use cases for Result
include:
- Rethrowing exceptions as an assertion:
Result.attempt(...).orElseAssert()
(similar totry!
in Swift) - Returning an empty optional on error:
Result<T, E>.attempt(...).getValue() // Optional<T>
(similar totry?
in Swift)
Result
isn't published in any repositories, so Jitpack is probably your best bet:
repositories {
maven { url 'https://jitpack.io' }
}
dependencies {
...
compile 'com.github.bgerstle:result-java:master-SNAPSHOT'
}
You can substitute a tag or commit with master-SNAPSHOT
if you don't to be on master.
With Java 11 or above installed, from the command line:
./gradlew assemble
With Java 11 or above installed, from the command line:
./gradlew check -i
Or, run all the tests in IntelliJ using a JUnit Run Configuration.