Interface Result<E,T>
- All Known Implementing Classes:
Result.Err, Result.Ok
T or fails with an E.
Compare with Vavr's Either or Try: this type discards the implicit
"left/right" naming convention in favour of explicit Result.Ok and Result.Err cases
that pattern-match cleanly on Java 21+ switch.
Idiomatic use:
Result<AuthError, User> result = fetchUser(id);
String message = switch (result) {
case Ok<AuthError, User> ok -> "Welcome, " + ok.value().name();
case Err<AuthError, User> err -> "Auth failed: " + err.error().reason();
};
The <E, T> type parameters are carried in both variants even though
Result.Err doesn't use T and Result.Ok doesn't use E. This is the
standard Scala / Rust shape for ADTs; the unused parameter survives as a phantom so
the compiler can prove exhaustiveness in pattern matches without unchecked casts.
-
Nested Class Summary
Nested ClassesModifier and TypeInterfaceDescriptionstatic interfaceThe unwrapping handle passed tobinding(Function).static final recordResult.Err<E,T> Failure case: carries anE.static final recordSuccess case: carries aT. -
Method Summary
Modifier and TypeMethodDescriptionstatic <E,T> Result <E, T> Run aCallableand capture any thrown exception as a typedResult.Err.static <E,T> Result <E, T> binding(Function<? super Result.Binder<E>, ? extends T> block) Sequence severalResult-returning calls as straight-line code, unwrapping each success and short-circuiting on the first failure.static <E,T> Result <E, T> err(E error) Build a failure.errValue()Returns the error ifResult.Err, else empty.Sequential composition: chain another operation that itself may fail.default <R> RFold both cases into a single value.static <E,T> Result <E, T> fromOptional(Optional<? extends T> maybe, Supplier<? extends E> ifEmpty) Lift anOptionalinto aResult: a present value becomesResult.Ok, an emptyOptionalbecomesResult.ErrcarryingifEmpty.get().default TProvide a fallback when this isResult.Err.default TgetOrElseGet(Function<? super E, ? extends T> fallback) Provide a lazy fallback when this isResult.Err.default booleanisErr()trueif this isResult.Err.default booleanisOk()trueif this isResult.Ok.Transform the success value; preserve the error.Transform the error; preserve the success value.static <E,T> Result <E, T> ok(T value) Build a success.okValue()Returns the success value ifResult.Ok, else empty.default <X extends RuntimeException>
TorElseThrow(Function<? super E, ? extends X> mapper) Throw a custom exception on failure; return the value otherwise.Recover from a failure with anotherResult.Observe the success value (logging, metrics) and pass the result through unchanged.Observe the error (logging, metrics) and pass the result through unchanged.zip(Result<E, U> other, BiFunction<? super T, ? super U, ? extends R> f) Combine two results into one via a binary function; both must beResult.Ok.
-
Method Details
-
ok
Build a success. -
err
Build a failure. -
attempt
static <E,T> Result<E,T> attempt(Callable<? extends T> body, Function<? super Throwable, ? extends E> onThrow) Run aCallableand capture any thrown exception as a typedResult.Err.The body is a
Callable(rather than aSupplier) precisely so it may throw checked exceptions: this method exists to wrap boundaries that talk to APIs which still throw (legacy stdlib, third-party libraries). The error mapper translates the thrownThrowableinto anE.attemptitself never throws; everyThrowablebecomes anResult.Err.Composing several fallible steps. When you have a sequence of dependent operations that each throw on failure, prefer one
attemptwrapping straight-line code over a tower of nestedflatMapcalls. On a virtual thread these are ordinary blocking calls; the first one to throw short-circuits the rest, and the singleonThrowmapper turns whatever was thrown into your error type:
This reads like do-notation but needs no monad: it is the fforj-idiomatic replacement forResult<AppError, Conditions> conditions = Result.attempt(() -> { var location = geocoder.locate(query); // throws on failure var coords = geocoder.coordinates(location); var weather = weatherApi.current(coords); // never reached if locate() threw return Conditions.of(location, weather); }, AppError::from);flatMap-in-flatMapwhen the steps are effectful. UseflatMap/zipinstead when the steps are pureResultvalues rather than throwing calls.Interruption. If the body throws
InterruptedException, the thread's interrupt status is re-asserted before the exception is mapped to anResult.Err.attemptstays total, but the cooperative-cancellation signal is preserved for the surrounding scope (Retry, a structured-concurrency scope, or any other blocking call further up). Note thatThrowablecapture includesErrors; if you don't want to handle those as values, rethrow fromonThrow.Binding aborts pass through. The control-flow abort used by
binding(Function)andValidated.accumulateis not captured: if the body short-circuits an enclosing block (bind.on(...)of anErr,Bound.value()of a failed binding), the abort propagates throughattemptuntouched instead of being mapped to a meaninglessErr. -
fromOptional
Lift anOptionalinto aResult: a present value becomesResult.Ok, an emptyOptionalbecomesResult.ErrcarryingifEmpty.get().The error is caller-supplied because emptiness alone carries no reason; only the caller knows what an absent value means in their domain. The supplier is evaluated lazily, so no error is built when the value is present. This is the bridge from
Optional-returning APIs (lookups,NonEmptyList.fromList, etc.) into aResultpipeline; the reverse direction isokValue()/errValue().Result<AppError, NonEmptyList<X>> r = // AppError is your own type Result.fromOptional(NonEmptyList.fromList(xs), AppError.NoCandidates::new); -
binding
Sequence severalResult-returning calls as straight-line code, unwrapping each success and short-circuiting on the first failure. Do-notation forResult.Inside the block you call
bind.on(...)on anyResult<E, ?>; it hands back the raw success value, so you never pattern-match or nestflatMap. The firstResult.Erraborts the rest of the block and becomes the result:Result<String, Integer> total = Result.binding(bind -> { int a = bind.on(parsePositive("3")); // Ok -> 3 int b = bind.on(parsePositive("4")); // Ok -> 4 int c = bind.on(parsePositive("-1")); // Err -> aborts here return a + b + c; // never reached }); // total == Result.err("not positive: -1")Use this when your steps already return
Result. When the steps instead throw, reach forattempt(Callable, Function); the two compose (wrap a throwing call inattempt, thenbind.onits result).How it short-circuits, and the one caveat
bind.onaborts the block by throwing a private, stack-trace-free control-flow exception thatbindingcatches at the boundary. Two consequences to know:- A broad
try { ... } catch (RuntimeException e)around abind.oncall inside the block will swallow the short-circuit and break the abort. Don't wrap bound calls in catch-all handlers. - Steps that throw a genuine exception (rather than returning
Err) are not captured here; the throwable propagates out ofbinding. Useattempt(Callable, Function)for those.
bindingcalls are safe; each abort carries the identity of the block that created it and is caught only by that block's boundary, so using an outer binder inside an inner block aborts the outer block, as it should.- Parameters:
block- receives aResult.Binderand returns the composed success value.- Returns:
Result.Okof the block's return value, or the firstResult.Errthatbind.onencountered.
- A broad
-
isOk
default boolean isOk()trueif this isResult.Ok. -
isErr
default boolean isErr()trueif this isResult.Err. -
okValue
Returns the success value ifResult.Ok, else empty.Named
okValuerather thanvalueso it does not collide with theResult.Ok.value()record accessor (which returns the rawT); this query lifts that into anOptionalfor call sites that don't pattern-match. -
errValue
Returns the error ifResult.Err, else empty.Named
errValuerather thanerrorso it does not collide with theResult.Err.error()record accessor (which returns the rawE); this query lifts that into anOptionalfor call sites that don't pattern-match. -
map
-
mapErr
-
tap
-
tapErr
-
flatMap
-
zip
-
getOrElse
Provide a fallback when this isResult.Err. The fallback must not be null. -
getOrElseGet
Provide a lazy fallback when this isResult.Err. -
recover
-
orElseThrow
Throw a custom exception on failure; return the value otherwise. -
fold
-