Interface Result<E,T>

All Known Implementing Classes:
Result.Err, Result.Ok

public sealed interface Result<E,T> permits Result.Ok<E,T>, Result.Err<E,T>
Sum type for an operation that either succeeds with a 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.

  • Method Details

    • ok

      static <E,T> Result<E,T> ok(T value)
      Build a success.
    • err

      static <E,T> Result<E,T> err(E error)
      Build a failure.
    • attempt

      static <E,T> Result<E,T> attempt(Callable<? extends T> body, Function<? super Throwable, ? extends E> onThrow)
      Run a Callable and capture any thrown exception as a typed Result.Err.

      The body is a Callable (rather than a Supplier) 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 thrown Throwable into an E. attempt itself never throws; every Throwable becomes an Result.Err.

      Composing several fallible steps. When you have a sequence of dependent operations that each throw on failure, prefer one attempt wrapping straight-line code over a tower of nested flatMap calls. On a virtual thread these are ordinary blocking calls; the first one to throw short-circuits the rest, and the single onThrow mapper turns whatever was thrown into your error type:

      Result<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);
      
      This reads like do-notation but needs no monad: it is the fforj-idiomatic replacement for flatMap-in-flatMap when the steps are effectful. Use flatMap/zip instead when the steps are pure Result values rather than throwing calls.

      Interruption. If the body throws InterruptedException, the thread's interrupt status is re-asserted before the exception is mapped to an Result.Err. attempt stays 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 that Throwable capture includes Errors; if you don't want to handle those as values, rethrow from onThrow.

      Binding aborts pass through. The control-flow abort used by binding(Function) and Validated.accumulate is not captured: if the body short-circuits an enclosing block (bind.on(...) of an Err, Bound.value() of a failed binding), the abort propagates through attempt untouched instead of being mapped to a meaningless Err.

    • fromOptional

      static <E,T> Result<E,T> fromOptional(Optional<? extends T> maybe, Supplier<? extends E> ifEmpty)
      Lift an Optional into a Result: a present value becomes Result.Ok, an empty Optional becomes Result.Err carrying ifEmpty.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 a Result pipeline; the reverse direction is okValue() / errValue().

      Result<AppError, NonEmptyList<X>> r =                     // AppError is your own type
          Result.fromOptional(NonEmptyList.fromList(xs), AppError.NoCandidates::new);
      
    • binding

      static <E,T> Result<E,T> binding(Function<? super Result.Binder<E>, ? extends T> block)
      Sequence several Result-returning calls as straight-line code, unwrapping each success and short-circuiting on the first failure. Do-notation for Result.

      Inside the block you call bind.on(...) on any Result<E, ?>; it hands back the raw success value, so you never pattern-match or nest flatMap. The first Result.Err aborts 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 for attempt(Callable, Function); the two compose (wrap a throwing call in attempt, then bind.on its result).

      How it short-circuits, and the one caveat

      bind.on aborts the block by throwing a private, stack-trace-free control-flow exception that binding catches at the boundary. Two consequences to know:

      • A broad try { ... } catch (RuntimeException e) around a bind.on call 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 of binding. Use attempt(Callable, Function) for those.
      Nested binding calls 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 a Result.Binder and returns the composed success value.
      Returns:
      Result.Ok of the block's return value, or the first Result.Err that bind.on encountered.
    • isOk

      default boolean isOk()
      true if this is Result.Ok.
    • isErr

      default boolean isErr()
      true if this is Result.Err.
    • okValue

      default Optional<T> okValue()
      Returns the success value if Result.Ok, else empty.

      Named okValue rather than value so it does not collide with the Result.Ok.value() record accessor (which returns the raw T); this query lifts that into an Optional for call sites that don't pattern-match.

    • errValue

      default Optional<E> errValue()
      Returns the error if Result.Err, else empty.

      Named errValue rather than error so it does not collide with the Result.Err.error() record accessor (which returns the raw E); this query lifts that into an Optional for call sites that don't pattern-match.

    • map

      default <U> Result<E,U> map(Function<? super T, ? extends U> f)
      Transform the success value; preserve the error.
    • mapErr

      default <F> Result<F,T> mapErr(Function<? super E, ? extends F> f)
      Transform the error; preserve the success value.
    • tap

      default Result<E,T> tap(Consumer<? super T> observer)
      Observe the success value (logging, metrics) and pass the result through unchanged. The observer runs only on Result.Ok; it cannot alter the result.
    • tapErr

      default Result<E,T> tapErr(Consumer<? super E> observer)
      Observe the error (logging, metrics) and pass the result through unchanged. The observer runs only on Result.Err; it cannot alter the result.
    • flatMap

      default <U> Result<E,U> flatMap(Function<? super T, ? extends Result<E,U>> f)
      Sequential composition: chain another operation that itself may fail.
    • zip

      default <U,R> Result<E,R> zip(Result<E,U> other, BiFunction<? super T, ? super U, ? extends R> f)
      Combine two results into one via a binary function; both must be Result.Ok.
    • getOrElse

      default T getOrElse(T fallback)
      Provide a fallback when this is Result.Err. The fallback must not be null.
    • getOrElseGet

      default T getOrElseGet(Function<? super E, ? extends T> fallback)
      Provide a lazy fallback when this is Result.Err.
    • recover

      default Result<E,T> recover(Function<? super E, ? extends Result<E,T>> recovery)
      Recover from a failure with another Result.
    • orElseThrow

      default <X extends RuntimeException> T orElseThrow(Function<? super E, ? extends X> mapper)
      Throw a custom exception on failure; return the value otherwise.
    • fold

      default <R> R fold(Function<? super E, ? extends R> onErr, Function<? super T, ? extends R> onOk)
      Fold both cases into a single value.