Version note: Rewritten September 4, 2026. Syntax and concurrency behavior depend on the Scala and library versions selected.

Error handling represents and responds to expected failure. Fault tolerance is a system’s ability to continue an acceptable service or recover under specified faults. Types help make failures visible, but no Scala construct alone creates resilience.

Choose an error representation

  • Option[A] represents presence or absence when no error detail is required.
  • Either[E, A] represents a typed failure or success and supports domain-specific error information.
  • Try[A] captures non-fatal exceptions from exception-throwing APIs; it is not a substitute for a deliberate domain error model.
  • Exceptions remain appropriate for some programming defects or APIs, but broad catching can hide cancellation, interruption, or fatal runtime conditions.
sealed trait ReadError
case object MissingPath extends ReadError
final case class InvalidRecord(line: Int, reason: String) extends ReadError

def parse(lineNo: Int, raw: String): Either[ReadError, Record] =
  decode(raw).left.map(message => InvalidRecord(lineNo, message))

Keep enough context to investigate without logging secrets or sensitive records. Decide whether invalid records fail the batch, enter a protected reject stream, or continue under an approved threshold.

Bound remote operations

Use deadlines/timeouts and cancellation supported by the exact concurrency library. Retry only transient, classified failures with bounded attempts, backoff, jitter, and a total time budget. Respect server retry guidance. Do not retry validation failures or non-idempotent writes blindly.

Make side effects safe

Use stable request/event identifiers, idempotency keys, transactional writes where supported, deduplication windows, and reconciliation. “Exactly once” is scoped to a boundary and failure model; an acknowledged timeout can leave outcome ambiguity that requires a status check.

Contain and recover

Bulkheads, circuit breakers, supervision, queues, and backpressure address different failure modes. A circuit breaker limits repeated calls to a failing dependency; it does not repair that dependency. Supervision semantics differ across actor/effect systems. Configure from service objectives and test overload, dependency loss, restart, and state recovery.

Test the failure contract

  1. Unit-test typed error branches and invariants.
  2. Inject timeouts, resets, malformed input, partial reads/writes, duplicates, and reordered events.
  3. Verify retry limits, cancellation, cleanup, reconciliation, and no secret leakage.
  4. Measure user-visible availability, latency, error budget, queue depth, recovery time, and data loss against objectives.
  5. Rehearse backup restoration and incident decision paths.

Apply these patterns in Scala data-intensive applications, extend them to Scala and Spark pipelines, and compare boundaries with microservices architecture patterns.