Skip to main content
Version: 2.x

Introduction to Error Management in ZIO

ZIO's error management model is built around three interlocking types: Cause[E], Exit[E, A], and the error channel of ZIO[R, E, A]. Together they give every ZIO program a precise, type-safe account of every failure, unhandled exception, and fiber interruption that can occur โ€” and a rich set of operators for recovering from each. Most readers encounter Cause[E] through recovery operators such as ZIO#catchAllCause and ZIO#sandbox rather than by constructing it directly; it is the runtime representation that the ZIO fiber runtime manages internally. Their structural shapes are:

sealed abstract class Cause[+E] // runtime error graph: failures, defects, interruptions
sealed trait Exit[+E, +A] // completed fiber result: Success or Failure(cause)
sealed trait ZIO[-R, +E, +A] // effect with a typed error channel E

Overviewโ€‹

The error management area is organized into six topics, each covering a distinct concern:

  • Three Types of Errors โ€” the taxonomy of typed failures (Failure), untyped runtime exceptions (Defect), and catastrophic JVM errors (Fatal) that ZIO distinguishes at both the type-system and runtime levels.
  • Core Concepts โ€” the typed-error guarantee, what it means for an effect to be "unexceptional", and how sequential versus parallel composition affects the error structure.
  • Error Channel Operations โ€” mapping, filtering, refining, flipping, and otherwise transforming the error channel without necessarily recovering from it.
  • Recovering From Errors โ€” catching typed failures, folding over results, retrying with a policy, sandboxing defects, timing out, and falling back to alternative effects.
  • Error Accumulation โ€” collecting all failures from a collection of effects without short-circuiting, using ZIO.validate and related combinators.
  • Best Practices โ€” modelling domain errors as sealed ADTs, keeping defects out of the typed channel, avoiding reflexive logging, and using union types (Scala 3) for lightweight error composition.

How They Work Togetherโ€‹

The three core types interact through a single data flow: constructors create Cause leaf nodes, composition combines them into a Cause graph, the fiber runtime records the graph in an Exit value, and recovery operators intercept the graph at different granularities. Here is the step-by-step workflow:

  1. ZIO.fail(e) produces a Cause.Fail(e, trace) node; ZIO.die(t) produces Cause.Die(t, trace); ZIO.interrupt produces Cause.Interrupt(fiberId, trace).
  2. When two sequential effects both fail, their causes are joined with ++, producing Cause.Then(left, right) โ€” a node that preserves temporal ordering.
  3. When two parallel effects both fail, their causes are joined with &&, producing Cause.Both(left, right) โ€” a node that records concurrent failures without imposing an order.
  4. When a fiber finishes, the runtime wraps the outcome in an Exit: either Exit.Success(value) for a successful result or Exit.Failure(cause) carrying the full Cause[E] graph.
  5. Recovery operators such as ZIO#catchAll and ZIO#catchSome intercept only the typed E channel โ€” they match Cause.Fail nodes but leave Cause.Die and Cause.Interrupt untouched.
  6. sandbox promotes the full Cause[E] into the typed error channel, producing ZIO[R, Cause[E], A], so any catch* operator can then pattern-match against Cause.Die, Cause.Interrupt, Cause.Then, or Cause.Both.
  7. ZIO#unsandbox (or the companion-object form ZIO.unsandbox(v)) reverts the sandbox, moving Cause[E] back out of the typed channel and restoring the effect's original E type.

The diagram below maps this data flow from construction through composition, fiber exit, and recovery:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ ZIO.fail โ”‚ โ”‚ ZIO.die โ”‚ โ”‚ ZIO.interrupt โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ”‚ โ”‚ โ”‚
โ–ผ โ–ผ โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Cause.Fail โ”‚ โ”‚ Cause.Die โ”‚ โ”‚Cause.Interrupt โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ”‚ (++) โ”‚ (&&) โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ–ผ โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Cause.Then / Cause.Both โ”‚
โ”‚ (sequential/parallel graph) โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ–ผ โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Exit.Failure(cause) โ”‚ โ”‚ Exit.Success(value) โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ”‚
โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ catchAll (typed E only) โ”‚
โ”‚ catchAllCause (full Cause[E] graph) โ”‚
โ”‚ sandbox โ†’ promotes Cause[E] to E channel โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ”‚
โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ recovered ZIO[R, E2, A] effect โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

foldCauseZIO is the primitive from which all catch* and fold* operators are derived โ€” every recovery method in ZIO is ultimately expressed in terms of it.

Type Relationships:

  • Cause[E] is the runtime representation of all three error channels; the type parameter E covers only the typed-failure dimension, while Cause.Die and Cause.Interrupt nodes are always untyped.
  • Exit[E, A] is a sealed subtype of ZIO[Any, E, A] โ€” an Exit value can be used anywhere a ZIO is expected without explicit lifting.
  • Exit.Failure(cause) wraps the full Cause[E] graph; Exit.Success(a) carries the result value of a successfully completed fiber.
  • Cause.Then preserves temporal ordering (sequential failures); Cause.Both preserves concurrency (parallel failures); Cause.Stackless suppresses stack-trace rendering for its sub-cause.
  • foldCauseZIO is the primitive from which all other catch* and fold* operators are derived; every other recovery combinator calls it internally.

To see these types working together, consider the sandbox/unsandbox pattern โ€” the standard approach when you need to recover from a defect or inspect the full Cause structure:

import zio._

val effect: ZIO[Any, String, String] =
ZIO.succeed("primary result") *> ZIO.fail("Oh uh!")

val recovered: ZIO[Any, String, String] =
effect
.sandbox // ZIO[Any, Cause[String], String]
.catchSome { case Cause.Fail(_, _) =>
ZIO.succeed("fallback result") // matches typed failure only
}
.unsandbox // ZIO[Any, String, String]

After sandbox promotes the entire Cause[String] into the error channel, catchSome can pattern-match on Cause.Fail specifically โ€” leaving any Cause.Die or Cause.Interrupt nodes untouched. Calling unsandbox at the end restores the effect's String error type.