Result
is a value that can be either Ok
or Err
, to signal whether an operation succeeded or failed. Each variant can contain data, e.g. Result<User, ErrMsg>
contains a User
if ok, and ErrMsg
when it fails.
It can be used as a return value from functions to indicate if they succeeded or failed, similar to Optional
, but with data about why it failed.
Java-result is feature-complete and can be used in Java 15+. It has extensive unit test coverage, but limited real-world testing.
Functions can return Result
to indicate whether they failed and why:
@Nonnull
public static Result<Integer, DivError> divide(@Nullable Integer numerator, @Nullable Integer divisor) {
if (null == numerator) {
return Result.err(DivError.NUMERATOR_NULL);
}
if (null == divisor) {
return Result.err(DivError.DIVISOR_NULL);
}
if (0 == divisor) {
return Result.err(DivError.DIVISOR_ZERO);
}
return Result.ok(numerator / divisor);
}
-
Only use the value if successful
Result<Integer, DivError> costPerPerson = divide(cost, people); if (costPerPerson.isOk()) { sendMessage("You need to pay " + costPerPerson.get()); }
or more tersely:
divide(cost, people) .ifOk(costPerPerson -> sendMessage("You need to pay " + costPerPerson));
-
Chain operations, transforming the results only if it is successful:
divide(9, 0).map(result -> result / 2) // Err(DIVISOR_ZERO)
of if the transformation can also fail:
divide(8, 2).flatMap(res -> divide(res, 2)) // Ok(2)
-
Fallback to a default in case of failure:
divide(8, 0).withoutErr().orElse(1) // Ok(1) divide(8, 0).withoutErr().orElseGet(() -> calculateFallback()) // calculateFallback() computed only if failed divide(8, 0).withoutErr().recover(err -> calculateFallback2(err)) // calculateFallback2(err) computed only if failed
-
From exception to
Result
:Result<String, Exception> userName = Result.attempt(() -> findUserName());
-
If you have decided to not handle errors:
divide(8, divisor).getOrThrow("if you see this, sorry...") // throws
-
Handle success result or adjust type:
Result<String, Exception> userNameResult = Result.attempt(() -> findUserName()); if (userNameResult instanceof Ok<String, Exception> userName) { return doSomethingWithUsername(userName.get()); } else { return userNameResult.adaptOk(); // Changes the ok type, which is safe because this is a failed Tesult }
-
Keep only success results, because
Result.stream
contains only the success value or nothing:List<Result<Integer, String>> list = List.of(ok(1), ok(2), err("problem"), ok(4)); List<Integer> successesOnly = list.stream() .flatMap(Result::stream).toList(); // [1, 2, 4]
-
Get all success values if all results are all successful, or the first error otherwise:
List<Result<Integer, String>> list = List.of(ok(1), ok(2), err("problem"), ok(4)); Result<List<Integer>, String> listResult = Result.transpose(list); // Err(problem)
There is also a
Stream
collector that does the same thing:Result<List<Integer>, DivError> streamResult = Stream.of(2, 1, 0, -1, -2) .map(nr -> divide(10, nr)) .collect(ResultCollector.toList()); // Err(DIVISOR_ZERO)
There is a lot more, have a look at the source.
Java-result is available on Central: nl.markv.result.
Add this dependency:
<dependency>
<groupId>nl.markv</groupId>
<artifactId>result</artifactId>
<version>1.1.0</version>
</dependency>
For Java 15/16 this uses preview features. Java 14 and below are not supported.
For Java 15+, add this dependency:
implementation 'nl.markv:result:1.1.0'
For Java 15/16 this uses preview features. Java 14 and below are not supported.
With sealed interfaces in Java 15 (preview feature), it finally has decent support for sum types - algebraic types that can have one out of a finite set of values. They are sometimes called unions or composite types.
You can think of it as an enhanced enum
, where each variant is a different subtype, instead of single instance. Each variant can have a different structure, and can have any number of instances.
Many languages that support sum types, like Kotlin, Haskell, Rust, Swift, C++ or others, have some kind of type that indicates one of two options - for example, success or failure. Java has Optional
, but that cannot contain an error value.
Result is a popular example of such types, which has two variants: one for success and one for failure. It can be used for error handling.
If you are familiar with monads, Result
is a monad with unit operations ok
/err
, bind operation map
/mapErr
, and a flattening operation flatMap
/flatMapErr
or flatten
.