Inspired by 🦀 Rust
This library implements a design pattern called Result
.
It provides a convenient representation of an operation outcome.
By wrapping return types into Result
developers can gracefully react to both success - Ok
and failure - Err
.
Errors as values force programmers to deal with undesirable behavior at the earliest stage which results in a more reliable, resilient and robust system.
Maven:
<dependency>
<groupId>io.github.dcadea</groupId>
<artifactId>jresult</artifactId>
<version>${version}</version>
</dependency>
Gradle:
dependencies {
implementation 'io.github.dcadea:jresult:${version}'
}
Result<String, Exception> readFile(String path) {
try {
return Result.ok(Files.readString(Paths.get(path)));
} catch (IOException e) {
return Result.err(e);
}
}
<T> Result<T, Exception> parseJson(String json, Class<T> clazz) {
try {
return Result.ok(objectMapper.readValue(json, clazz));
} catch (IOException e) {
return Result.err(e);
}
}
void main() {
var result = readFile("some/path/awesome.json")
.andThen(content -> parseJson(content, Model.class));
switch (result) {
case Ok(Model model) -> /* handle ok value */;
case Err(Exception e) -> /* react to error */;
}
}
To create a Result
, you can use the static methods ok
and err
:
import io.github.dcadea.jresult.Result;
var ok = Result.ok("success");
var err = Result.err("failure");
var result = Result.ok("success");
if (result.isOk()) { /* is an Ok variant holding a value */ }
if (result.isErr()) { /* is an Err variant holding an error value */ }
if (result.isEmpty()) { /* is an Ok variant with null as value */ }
// Additionally you can test if value of any rasult variant satisfies given predicate
if (result.isOkAnd(v -> v.startsWith("su"))) { /* good to go */ }
if (result.isErrAnd(e -> e.reason() == TIMEOUT)) { /* ... */ }
A straightforward way of accessing value of a Result is by calling respective unwrap
method.
Note that these methods throw IllegalStateException
when Result is not in desired state.
You can customize exception message by calling alternative method - expect
.
var okValue = result.unwrap(); // throws exception when result is Err
var errValue = result.unwrapErr(); // throws exception when result is Ok
var okValue = result.expect("this should never happen");
var errValue = result.expectErr("why this is ok?");
Have a fallback?
var result = Result.err("bad!");
var eager = result.unwrapOr(5);
var lazy = result.unwrapOrElse(() -> 5);
If you don't care about errors of a result, there is a way to convert it to an Optional by calling ok
.
Same thing could be achieved when there is no point in success, just call err
.
Result<Integer, String> result = Result.ok(5);
Optional<Integer> opt = result.ok(); // Optional.of(5)
Optional<String> opt = result.err(); // Optional.empty()
Result<Integer, String> result = Result.err("crash");
Optional<Integer> opt = result.ok(); // Optional.empty()
Optional<String> opt = result.err(); // Optional.of("crash")
Works with Java 21+ only.
if (result instanceof Ok(String value)) { /* use deconstructed value here */ }
if (result instanceof Err(Kaboom boom)) { /* use deconstructed error value here */ }
// or walk through all variants using switch
var value = switch (result) {
case Ok(String v) -> v;
case Err(Kaboom boom) -> {
logger.log(boom);
yield "default";
};
Having two or more results you can apply logical short-circuit and
& or
operations.
ok(5).and(err("error")); // Err("error")
err("error").and(ok(5)); // Err("error")
err("error1").and(err("error2")); // Err("error1")
ok(5).and(ok(10)); // Ok(10)
ok(5).or(err("error")); // Ok(5)
err("error").or(ok(5)); // Ok(5)
err("error1").or(err("error2")); // Err("error2")
ok(5).or(ok(10)); // Ok(5)
You can use lazy methods andThen
& orElse
to chain results.
ok(5).andThen(v -> err("error")); // Err("error")
err("error").andThen(v -> ok(v + 10)); // Err("error")
err("error1").andThen(v -> err("error2")); // Err("error1")
ok(5).andThen(v -> ok(v + 10)); // Ok(15)
ok(5).orElse(e -> err("error")); // Ok(5)
err("error").orElse(e -> ok(10)); // Ok(10)
err("error1").orElse(e -> err("error2")); // Err("error2")
ok(5).orElse(e -> ok(10)); // Ok(10)
Note that
andThen
passes value ofOk
variant butorElse
passes value ofErr
variant downstream.
Take a peek at ok or error values by calling respective inspect
method.
var res = Result.ok(5);
res.inspect(System.out::println); // prints 5
res.inspectErr(System.out::println); // does nothing
Result<Integer, String> res = Result.err("error");
res.inspect(System.out::println); // does nothing
res.inspectErr(System.out::println); // prints "error"
This project is licensed under Apache 2.0 and MIT Licenses - see the corresponding file for details.