or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

application.mdconcurrency.mdcore-effects.mddependency-injection.mderror-handling.mdindex.mdmetrics.mdresource-management.mdservices.mdstm.mdstreams.mdtesting.md

error-handling.mddocs/

0

# Error Handling

1

2

ZIO provides a comprehensive error model with rich failure causes and composable retry/repeat scheduling for building resilient applications that gracefully handle failures and recover automatically.

3

4

## Capabilities

5

6

### Cause - Rich Error Model

7

8

ZIO's Cause type provides a rich representation of failure modes including typed errors, defects, and interruptions.

9

10

```scala { .api }

11

/**

12

* Represents the cause of a ZIO effect failure with rich error information

13

*/

14

sealed abstract class Cause[+E] {

15

/** Extract all typed failures from this cause */

16

def failures: List[E]

17

18

/** Extract all defects (untyped failures) from this cause */

19

def defects: List[Throwable]

20

21

/** Extract all fiber IDs that caused interruption */

22

def interruptors: Set[FiberId]

23

24

/** Get the first failure if any */

25

def failureOption: Option[E]

26

27

/** Get the first defect if any */

28

def dieOption: Option[Throwable]

29

30

/** Get the first interruption cause if any */

31

def interruptOption: Option[FiberId]

32

33

/** Extract either the failure or the remaining cause */

34

def failureOrCause: Either[E, Cause[Nothing]]

35

}

36

```

37

38

### Cause Construction and Composition

39

40

Create and combine causes to represent complex failure scenarios.

41

42

```scala { .api }

43

/**

44

* Combine two causes in parallel (both happened simultaneously)

45

*/

46

def &&[E1 >: E](that: Cause[E1]): Cause[E1]

47

48

/**

49

* Combine two causes sequentially (second happened after first)

50

*/

51

def ++[E1 >: E](that: Cause[E1]): Cause[E1]

52

53

/**

54

* Transform the typed error part of the cause

55

*/

56

def map[E1](f: E => E1): Cause[E1]

57

58

/**

59

* FlatMap over the cause structure

60

*/

61

def flatMap[E2](f: E => Cause[E2]): Cause[E2]

62

63

/**

64

* Replace all failures with a constant error

65

*/

66

def as[E1](e: => E1): Cause[E1]

67

68

// Factory methods

69

object Cause {

70

/** Empty cause (no failure) */

71

val empty: Cause[Nothing]

72

73

/** Create a cause from a typed failure */

74

def fail[E](error: E, trace: StackTrace = StackTrace.none): Cause[E]

75

76

/** Create a cause from a defect (untyped failure) */

77

def die(defect: Throwable, trace: StackTrace = StackTrace.none): Cause[Nothing]

78

79

/** Create a cause from fiber interruption */

80

def interrupt(fiberId: FiberId, trace: StackTrace = StackTrace.none): Cause[Nothing]

81

}

82

```

83

84

**Usage Examples:**

85

86

```scala

87

import zio._

88

89

// Creating different types of causes

90

val typedFailure = Cause.fail("Invalid input")

91

val defectCause = Cause.die(new RuntimeException("Unexpected error"))

92

val interruptCause = Cause.interrupt(FiberId.make(123))

93

94

// Combining causes

95

val combinedCause = Cause.fail("Error 1") && Cause.fail("Error 2")

96

val sequentialCause = Cause.fail("First error") ++ Cause.die(new Exception("Then this"))

97

98

// Analyzing cause structure

99

val analyzeFailure = (cause: Cause[String]) => {

100

val failures = cause.failures

101

val defects = cause.defects

102

val interruptions = cause.interruptors

103

104

s"Failures: ${failures.mkString(", ")}, Defects: ${defects.size}, Interruptions: ${interruptions.size}"

105

}

106

```

107

108

### Cause Predicates and Filtering

109

110

Query and filter causes to understand and handle specific failure types.

111

112

```scala { .api }

113

/**

114

* Check if the cause is empty (no failures)

115

*/

116

def isEmpty: Boolean

117

118

/**

119

* Check if cause contains typed failures

120

*/

121

def isFailure: Boolean

122

123

/**

124

* Check if cause contains defects

125

*/

126

def isDie: Boolean

127

128

/**

129

* Check if cause contains interruptions

130

*/

131

def isInterrupted: Boolean

132

133

/**

134

* Check if cause contains only interruptions

135

*/

136

def isInterruptedOnly: Boolean

137

138

/**

139

* Keep only defects, removing failures and interruptions

140

*/

141

def keepDefects: Option[Cause[Nothing]]

142

143

/**

144

* Remove all typed failures

145

*/

146

def stripFailures: Cause[Nothing]

147

148

/**

149

* Remove specific defects matching a partial function

150

*/

151

def stripSomeDefects(pf: PartialFunction[Throwable, Any]): Option[Cause[E]]

152

153

/**

154

* Filter the cause structure

155

*/

156

def filter(p: Cause[E] => Boolean): Cause[E]

157

```

158

159

**Usage Examples:**

160

161

```scala

162

// Cause analysis and filtering

163

val handleCause = (cause: Cause[AppError]) => {

164

if (cause.isInterruptedOnly) {

165

Console.printLine("Operation was cancelled")

166

} else if (cause.isFailure) {

167

Console.printLine(s"Business logic error: ${cause.failures.head}")

168

} else if (cause.isDie) {

169

Console.printLineError(s"System error: ${cause.defects.head.getMessage}")

170

} else {

171

Console.printLine("No errors occurred")

172

}

173

}

174

175

// Selective error handling

176

val recoverableErrors = cause.stripSomeDefects {

177

case _: TimeoutException => () // Remove timeout defects

178

case _: IOException => () // Remove IO defects

179

}

180

```

181

182

### Cause Debugging and Presentation

183

184

Tools for debugging and presenting cause information to users and developers.

185

186

```scala { .api }

187

/**

188

* Get the stack trace associated with this cause

189

*/

190

def trace: StackTrace

191

192

/**

193

* Get all stack traces in the cause

194

*/

195

def traces: List[StackTrace]

196

197

/**

198

* Add a stack trace to the cause

199

*/

200

def traced(trace: StackTrace): Cause[E]

201

202

/**

203

* Remove stack traces from the cause

204

*/

205

def untraced: Cause[E]

206

207

/**

208

* Convert cause to a human-readable string

209

*/

210

def prettyPrint: String

211

212

/**

213

* Convert cause to a single Throwable (for interop)

214

*/

215

def squash(implicit ev: E IsSubtypeOfError Throwable): Throwable

216

217

/**

218

* Convert cause to Throwable using custom function

219

*/

220

def squashWith(f: E => Throwable): Throwable

221

222

/**

223

* Get annotations attached to the cause

224

*/

225

def annotations: Map[String, String]

226

227

/**

228

* Get execution spans in the cause

229

*/

230

def spans: List[LogSpan]

231

232

/**

233

* Add annotations to the cause

234

*/

235

def annotated(anns: Map[String, String]): Cause[E]

236

237

/**

238

* Add execution spans to the cause

239

*/

240

def spanned(spans: List[LogSpan]): Cause[E]

241

```

242

243

**Usage Examples:**

244

245

```scala

246

// Debug information extraction

247

val debugFailure = (cause: Cause[String]) => for {

248

_ <- Console.printLineError("=== Failure Analysis ===")

249

_ <- Console.printLineError(cause.prettyPrint)

250

_ <- Console.printLineError(s"Stack traces: ${cause.traces.size}")

251

_ <- Console.printLineError(s"Annotations: ${cause.annotations}")

252

} yield ()

253

254

// Convert for external systems

255

val convertToThrowable = (cause: Cause[AppError]) => {

256

cause.squashWith {

257

case ValidationError(msg) => new IllegalArgumentException(msg)

258

case DatabaseError(msg) => new SQLException(msg)

259

case NetworkError(msg) => new IOException(msg)

260

}

261

}

262

```

263

264

### Schedule - Retry and Repeat Logic

265

266

Composable scheduling policies for retry and repeat operations with various timing strategies.

267

268

```scala { .api }

269

/**

270

* A composable schedule for retrying and repeating operations

271

* - Env: Environment required for scheduling decisions

272

* - In: Input type (usually the error type for retries)

273

* - Out: Output type (usually the retry count or delay)

274

*/

275

trait Schedule[-Env, -In, +Out] {

276

type State

277

278

/** Initial state of the schedule */

279

def initial: State

280

281

/** Determine the next step given current time, input, and state */

282

def step(now: OffsetDateTime, in: In, state: State): ZIO[Env, Nothing, (State, Out, Decision)]

283

}

284

285

/**

286

* Decision whether to continue or stop scheduling

287

*/

288

sealed trait Decision

289

object Decision {

290

case class Continue(interval: Intervals) extends Decision

291

case object Done extends Decision

292

}

293

```

294

295

### Schedule Composition Operators

296

297

Combine schedules using various operators to create complex retry policies.

298

299

```scala { .api }

300

/**

301

* Intersection - run both schedules and continue only if both want to continue

302

*/

303

def &&[Env1 <: Env, In1 <: In, Out2](that: Schedule[Env1, In1, Out2]): Schedule[Env1, In1, (Out, Out2)]

304

305

/**

306

* Union - run both schedules and continue if either wants to continue

307

*/

308

def ||[Env1 <: Env, In1 <: In, Out2](that: Schedule[Env1, In1, Out2]): Schedule[Env1, In1, (Out, Out2)]

309

310

/**

311

* Sequential composition - run this schedule, then that schedule

312

*/

313

def ++[Env1 <: Env, In1 <: In, Out2 >: Out](that: Schedule[Env1, In1, Out2]): Schedule[Env1, In1, Out2]

314

315

/**

316

* Compose outputs - pipe output of this schedule as input to next schedule

317

*/

318

def >>>[Env1 <: Env, Out2](that: Schedule[Env1, Out, Out2]): Schedule[Env1, In, Out2]

319

320

/**

321

* Either composition - try this schedule, fallback to that schedule

322

*/

323

def |||[Env1 <: Env, In1 <: In, Out2](that: Schedule[Env1, In1, Out2]): Schedule[Env1, In1, Either[Out, Out2]]

324

```

325

326

**Usage Examples:**

327

328

```scala

329

// Complex retry policy

330

val resilientRetry =

331

Schedule.exponential(100.millis) // Exponential backoff

332

.jittered // Add randomness

333

&& Schedule.recurs(5) // Max 5 attempts

334

&& Schedule.recurWhile[Throwable](_.isInstanceOf[TemporaryException])

335

336

// Fallback strategy

337

val retryWithFallback =

338

Schedule.exponential(1.second) && Schedule.recurs(3) // Primary strategy

339

++ Schedule.spaced(30.seconds) && Schedule.recurs(2) // Fallback strategy

340

341

// Complex condition-based retry

342

val smartRetry =

343

Schedule.recurWhile[DatabaseError] {

344

case ConnectionTimeout => true

345

case DeadlockDetected => true

346

case ConstraintViolation => false

347

} && Schedule.exponential(500.millis).jittered

348

```

349

350

### Common Schedule Patterns

351

352

Pre-built schedules for common retry and repeat scenarios.

353

354

```scala { .api }

355

// Basic schedules

356

/** Run forever */

357

val forever: Schedule[Any, Any, Long]

358

359

/** Run exactly once */

360

def once: Schedule[Any, Any, Unit]

361

362

/** Run n times */

363

def recurs(n: Long): Schedule[Any, Any, Long]

364

365

/** Always succeed with constant value */

366

def succeed[A](a: => A): Schedule[Any, Any, A]

367

368

/** Identity schedule (pass input as output) */

369

def identity[A]: Schedule[Any, A, A]

370

371

// Time-based schedules

372

/** Fixed duration delay */

373

def duration(duration: Duration): Schedule[Any, Any, Duration]

374

375

/** Fixed interval spacing */

376

def spaced(duration: Duration): Schedule[Any, Any, Long]

377

378

/** Fixed rate (accounting for execution time) */

379

def fixed(interval: Duration): Schedule[Any, Any, Long]

380

381

/** Exponential backoff */

382

def exponential(base: Duration, factor: Double = 2.0): Schedule[Any, Any, Duration]

383

384

/** Fibonacci sequence delays */

385

def fibonacci(one: Duration): Schedule[Any, Any, Duration]

386

387

/** Linear increasing delays */

388

def linear(base: Duration): Schedule[Any, Any, Duration]

389

390

// Conditional schedules

391

/** Continue while predicate is true */

392

def recurWhile[A](f: A => Boolean): Schedule[Any, A, A]

393

394

/** Continue until predicate is true */

395

def recurUntil[A](f: A => Boolean): Schedule[Any, A, A]

396

397

/** Collect inputs while predicate is true */

398

def collectWhile[A](f: A => Boolean): Schedule[Any, A, Chunk[A]]

399

400

/** Collect inputs until predicate is true */

401

def collectUntil[A](f: A => Boolean): Schedule[Any, A, Chunk[A]]

402

```

403

404

**Usage Examples:**

405

406

```scala

407

// Common retry patterns

408

val httpRetry = httpRequest.retry(

409

Schedule.exponential(100.millis) && Schedule.recurs(3)

410

)

411

412

val databaseRetry = databaseQuery.retry(

413

Schedule.fibonacci(1.second) && Schedule.recurs(5)

414

)

415

416

val periodicTask = taskExecution.repeat(

417

Schedule.fixed(30.minutes)

418

)

419

420

// Smart conditional retry

421

val apiRetry = apiCall.retry(

422

Schedule.recurWhile[HttpError] {

423

case HttpError(code) if code >= 500 => true // Server errors

424

case HttpError(429) => true // Rate limited

425

case _ => false // Client errors

426

} && Schedule.exponential(1.second).jittered && Schedule.recurs(3)

427

)

428

```

429

430

### Schedule Transformation and Customization

431

432

Transform and customize schedules for specific use cases and requirements.

433

434

```scala { .api }

435

/**

436

* Transform the output of the schedule

437

*/

438

def map[Out2](f: Out => Out2): Schedule[Env, In, Out2]

439

440

/**

441

* Transform output using a ZIO effect

442

*/

443

def mapZIO[Env1 <: Env, Out2](f: Out => URIO[Env1, Out2]): Schedule[Env1, In, Out2]

444

445

/**

446

* Transform the input to the schedule

447

*/

448

def contramap[In2](f: In2 => In): Schedule[Env, In2, Out]

449

450

/**

451

* Add delay to each schedule decision

452

*/

453

def addDelay(f: Out => Duration): Schedule[Env, In, Out]

454

455

/**

456

* Modify delays in the schedule

457

*/

458

def delayed(f: Duration => Duration): Schedule[Env, In, Out]

459

460

/**

461

* Add random jitter to delays

462

*/

463

def jittered: Schedule[Env, In, Out]

464

465

/**

466

* Add custom jitter

467

*/

468

def jittered(min: Double, max: Double): Schedule[Env, In, Out]

469

470

/**

471

* Continue while input satisfies predicate

472

*/

473

def whileInput[In1 <: In](f: In1 => Boolean): Schedule[Env, In1, Out]

474

475

/**

476

* Continue while output satisfies predicate

477

*/

478

def whileOutput(f: Out => Boolean): Schedule[Env, In, Out]

479

480

/**

481

* Continue until input satisfies predicate

482

*/

483

def untilInput[In1 <: In](f: In1 => Boolean): Schedule[Env, In1, Out]

484

485

/**

486

* Continue until output satisfies predicate

487

*/

488

def untilOutput(f: Out => Boolean): Schedule[Env, In, Out]

489

490

/**

491

* Collect all outputs

492

*/

493

def collectAll: Schedule[Env, In, Chunk[Out]]

494

495

/**

496

* Fold over outputs with an accumulator

497

*/

498

def fold[Z](z: Z)(f: (Z, Out) => Z): Schedule[Env, In, Z]

499

500

/**

501

* Count the number of repetitions

502

*/

503

def repetitions: Schedule[Env, In, Long]

504

```

505

506

**Usage Examples:**

507

508

```scala

509

// Custom schedule transformations

510

val adaptiveRetry = Schedule.exponential(100.millis)

511

.whileOutput(_ < 30.seconds) // Cap maximum delay

512

.jittered(0.1, 0.2) // 10-20% jitter

513

.repetitions // Track attempt count

514

515

// Conditional scheduling with state

516

val circuitBreakerSchedule = Schedule.recurs(3)

517

.whileInput[ServiceError] {

518

case ServiceUnavailable => true

519

case CircuitOpen => false

520

case _ => true

521

}

522

523

// Accumulating retry information

524

val retryWithLogging = Schedule.exponential(1.second)

525

.fold(List.empty[Attempt]) { (attempts, delay) =>

526

Attempt(System.currentTimeMillis(), delay) :: attempts

527

}

528

529

// Rate limiting schedule

530

val rateLimitedSchedule = Schedule.fixed(100.millis)

531

.whileOutput(_ => !rateLimitExceeded())

532

.mapZIO(_ => checkRateLimit())

533

```