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(); // 3Here's the play by play:
attempttogetthe optional value ofSystem.getenv(...), this yields aResult<String, Exception>which either contains aStringor an exception (in this case, aNoSuchElementException)flatMapthe initialResultby trying to construct aURI, which returns aResult<URI, Exception>, containing either aURIor 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 assembleWith Java 11 or above installed, from the command line:
./gradlew check -iOr, run all the tests in IntelliJ using a JUnit Run Configuration.