EitherOrBothPath
EitherOrBothPath<L, A> wraps EitherOrBoth<L, A> for
computations that succeed but may also carry non-fatal warnings. It is the railway companion to
ValidationPath: where ValidationPath is success or accumulated errors, EitherOrBothPath adds a
third outcome: a value and warnings.
- Creating EitherOrBothPath instances, including the
rightNel/leftNel/bothNelshortcuts - Short-circuit sequencing with
via(Left stops; Both carries warnings forward) - Parallel, collect-everything accumulation with
zipWithAccum/andAlso - Recovering from a fatal
Leftat the boundary - Converting to other paths, deciding what happens to warnings
Creation
The rightNel / leftNel / bothNel factories bake in NonEmptyList.semigroup(), so the common
case needs no Semigroup argument and no manual single-warning wrapping:
EitherOrBothPath<NonEmptyList<String>, Config> ok = Path.rightNel(config);
EitherOrBothPath<NonEmptyList<String>, Config> warned = Path.bothNel("deprecated key", config);
EitherOrBothPath<NonEmptyList<String>, Config> failed = Path.leftNel("config missing");
When you need a custom warning channel, supply the Semigroup explicitly:
EitherOrBothPath<String, Integer> p = Path.both("warn", 42, Semigroups.string("; "));
The Railway View
via runs a dependent step on the success track. A Right flows straight through; a Both carries its
warnings forward and accumulates any the next step adds; a Left derails and short-circuits the rest of
the chain. recover can switch a Left back onto the success track. (The accumulating zipWithAccum
mode, which collects every warning even across a Left, is covered below.)
Right ═══●═══════════════●═══▶ value
Both ═══●═══════════════●═══▶ value + warnings (via threads and accumulates them)
via f via g
╲ ╲ via returns Left
╲ ╲
Left ────●───────────────●───▶ fatal, f never runs
│
recover switch back to the success track
│
●═══▶ recovered Right
Two Modes of Composition
Like ValidationPath, EitherOrBothPath offers both short-circuit and accumulating composition.
Short-circuit (via)
via sequences dependent steps. A Left stops the chain; a Both carries its warnings forward and
accumulates any the next step produces:
EitherOrBothPath<NonEmptyList<String>, Integer> result =
Path.<String, Integer>bothNel("uses deprecated key", 8)
.via(value -> value < 10
? Path.bothNel("value is low", value)
: Path.rightNel(value));
result.run(); // Both([uses deprecated key, value is low], 8)
result.warnings(); // Just([uses deprecated key, value is low])
result.getOrElse(0); // 8
Accumulating (zipWithAccum)
zipWithAccum combines independent results and collects every warning, even across a fatal
Left (the Validated-style accumulation that the monadic operations deliberately do not do):
EitherOrBothPath<NonEmptyList<String>, String> name = Path.bothNel("name was trimmed", "Ada");
EitherOrBothPath<NonEmptyList<String>, Integer> age = Path.bothNel("age defaulted", 30);
EitherOrBothPath<NonEmptyList<String>, String> reg =
name.zipWithAccum(age, (n, a) -> n + " (" + a + ")");
// Both([name was trimmed, age defaulted], "Ada (30)")
andAlso / andThen are conveniences over zipWithAccum that keep one side's value while still
accumulating both sides' warnings.
The Chainable combinators (via, zipWith) follow the monad: a Left short-circuits and the other
side's warnings are dropped. The Accumulating combinators (zipWithAccum, andAlso) collect every
warning regardless. Choose by intent: sequential dependency → via; independent validations →
zipWithAccum.
Recovery
recover / recoverWith / orElse act on a fatal Left only; a Both is already a success and is
passed through unchanged:
Path.<String, Integer>leftNel("config missing").recover(errors -> 0).run(); // Right(0)
Path.<String, Integer>bothNel("deprecated", 42).recover(errors -> 0).run(); // Both([deprecated], 42)
To transform the warning type, use mapErrorWith(mapper, newSemigroup) (a new Semigroup is needed
for the new type).
Conversions
| Method | Effect |
|---|---|
toEitherPathDroppingWarnings() | Both becomes Right (warnings dropped) |
toEitherPathFailingOnWarnings() | Both becomes Left (warnings fatal) |
toValidationPath() | Both becomes Valid (warnings dropped) |
toMaybePath() / toOptionalPath() | keep the success value, discard the left channel |
toTryPath(errorToException) | Left becomes a failure; Right/Both a success |
At the HTTP Boundary (hkj-spring)
When returned from a Spring controller, EitherOrBothPath maps to:
Right→200 OKwith the value as the body.Both→200 OKwith the value as the body, and the warnings surfaced (never silently dropped) in theX-Hkj-Warningsresponse header.Left→ a 4xx/5xx response whose status is resolved by the configuredErrorStatusCodeStrategy.
When To Use It
| Need | Use |
|---|---|
| Success that may carry non-fatal warnings | EitherOrBothPath |
| Pure error accumulation, no partial value | ValidationPath |
| Clean success-or-failure, short-circuit | EitherPath |
- EitherOrBoth - the underlying type and its
flatMapcontract - ValidationPath - pure accumulating validation
- EitherPath - short-circuit success-or-failure
- NonEmptyList - the warning channel
Previous: ValidationPath Next: IOPath