0
# Exception Handling
1
2
Structured exception handling with coroutine-aware propagation, cancellation semantics, and supervisor patterns for fault-tolerant concurrent programming.
3
4
## Capabilities
5
6
### CoroutineExceptionHandler
7
8
Context element for handling uncaught exceptions in coroutines.
9
10
```kotlin { .api }
11
interface CoroutineExceptionHandler : CoroutineContext.Element {
12
/** Handles uncaught exception in coroutine context */
13
fun handleException(context: CoroutineContext, exception: Throwable)
14
15
companion object Key : CoroutineContext.Key<CoroutineExceptionHandler>
16
}
17
18
/** Creates exception handler from function */
19
inline fun CoroutineExceptionHandler(
20
crossinline handler: (CoroutineContext, Throwable) -> Unit
21
): CoroutineExceptionHandler
22
```
23
24
**Usage Examples:**
25
26
```kotlin
27
import kotlinx.coroutines.*
28
29
val customExceptionHandler = CoroutineExceptionHandler { context, exception ->
30
println("Caught exception in context $context: ${exception.message}")
31
32
// Log exception, send to crash reporting, etc.
33
when (exception) {
34
is IllegalArgumentException -> println("Invalid argument provided")
35
is RuntimeException -> println("Runtime error occurred")
36
else -> println("Unexpected error: ${exception::class.simpleName}")
37
}
38
}
39
40
fun main() = runBlocking {
41
// Exception handler for top-level coroutines
42
val job = GlobalScope.launch(customExceptionHandler) {
43
throw RuntimeException("Something went wrong!")
44
}
45
46
job.join()
47
48
// Exception handler with multiple coroutines
49
val supervisor = SupervisorJob()
50
val scope = CoroutineScope(Dispatchers.Default + supervisor + customExceptionHandler)
51
52
scope.launch {
53
throw IllegalArgumentException("Invalid input")
54
}
55
56
scope.launch {
57
delay(100)
58
println("This coroutine continues despite sibling failure")
59
}
60
61
delay(200)
62
supervisor.complete()
63
}
64
```
65
66
### SupervisorJob
67
68
Job that doesn't cancel when its children fail, enabling independent failure handling.
69
70
```kotlin { .api }
71
/** Creates supervisor job that isolates child failures */
72
fun SupervisorJob(parent: Job? = null): CompletableJob
73
74
/** Creates supervisor scope with isolated child failures */
75
suspend fun <T> supervisorScope(
76
block: suspend CoroutineScope.() -> T
77
): T
78
```
79
80
**Usage Examples:**
81
82
```kotlin
83
import kotlinx.coroutines.*
84
85
fun main() = runBlocking {
86
// SupervisorJob prevents child failures from cancelling parent
87
val supervisor = SupervisorJob()
88
89
launch(supervisor) {
90
// Child 1 - will fail
91
launch {
92
delay(100)
93
throw RuntimeException("Child 1 failed")
94
}
95
96
// Child 2 - will complete successfully
97
launch {
98
delay(200)
99
println("Child 2 completed successfully")
100
}
101
102
// Child 3 - will also complete
103
launch {
104
delay(300)
105
println("Child 3 completed successfully")
106
}
107
108
delay(500)
109
println("Parent completed - children failures were isolated")
110
}
111
112
delay(600)
113
supervisor.complete()
114
}
115
116
// supervisorScope example for parallel processing
117
suspend fun processItemsSafely(items: List<String>): List<String> = supervisorScope {
118
items.map { item ->
119
async {
120
when {
121
item == "fail" -> throw RuntimeException("Processing failed for $item")
122
item.isEmpty() -> throw IllegalArgumentException("Empty item")
123
else -> "Processed: $item"
124
}
125
}
126
}.mapNotNull { deferred ->
127
try {
128
deferred.await()
129
} catch (e: Exception) {
130
println("Failed to process item: ${e.message}")
131
null // Continue with other items
132
}
133
}
134
}
135
136
suspend fun supervisorScopeExample() {
137
val items = listOf("item1", "fail", "item3", "", "item5")
138
val results = processItemsSafely(items)
139
println("Successfully processed: $results")
140
}
141
```
142
143
### CancellationException
144
145
Special exception used for cooperative cancellation that doesn't propagate to parent.
146
147
```kotlin { .api }
148
/** Exception for cooperative cancellation */
149
open class CancellationException(
150
message: String? = null,
151
cause: Throwable? = null
152
) : IllegalStateException(message)
153
154
/** Throws CancellationException if job is cancelled */
155
fun Job.ensureActive()
156
157
/** Checks if context is active (not cancelled) */
158
val CoroutineContext.isActive: Boolean
159
160
/** Gets current job from context */
161
val CoroutineContext.job: Job
162
```
163
164
**Usage Examples:**
165
166
```kotlin
167
import kotlinx.coroutines.*
168
169
suspend fun cooperativeCancellation() {
170
repeat(1000) { i ->
171
// Check for cancellation periodically
172
if (!coroutineContext.isActive) {
173
println("Detected cancellation at iteration $i")
174
return
175
}
176
177
// Alternative: throws CancellationException if cancelled
178
coroutineContext.ensureActive()
179
180
// Simulate work
181
if (i % 100 == 0) {
182
println("Processing iteration $i")
183
}
184
185
// Suspending functions automatically check for cancellation
186
yield() // Yields execution and checks cancellation
187
}
188
}
189
190
fun main() = runBlocking {
191
val job = launch {
192
try {
193
cooperativeCancellation()
194
println("Completed all iterations")
195
} catch (e: CancellationException) {
196
println("Coroutine was cancelled: ${e.message}")
197
throw e // Re-throw to maintain cancellation semantics
198
}
199
}
200
201
delay(250) // Let it run for a while
202
job.cancel("Manual cancellation")
203
job.join()
204
205
// Custom cancellation exception
206
val customJob = launch {
207
try {
208
delay(1000)
209
} catch (e: CancellationException) {
210
println("Custom cleanup before cancellation")
211
throw e
212
}
213
}
214
215
delay(100)
216
customJob.cancel(CancellationException("Custom cancellation reason"))
217
customJob.join()
218
}
219
```
220
221
### Exception Propagation
222
223
Understanding how exceptions propagate through coroutine hierarchies.
224
225
```kotlin { .api }
226
/** Handles coroutine exception with context */
227
fun handleCoroutineException(context: CoroutineContext, exception: Throwable)
228
229
/** Try-catch equivalents for coroutines */
230
suspend fun <T> runCatching(block: suspend () -> T): Result<T>
231
232
/** Non-cancellable execution block */
233
suspend fun <T> NonCancellable.run(block: suspend () -> T): T
234
```
235
236
**Usage Examples:**
237
238
```kotlin
239
import kotlinx.coroutines.*
240
241
// Exception propagation in regular job hierarchy
242
suspend fun regularJobExceptionPropagation() = coroutineScope {
243
try {
244
val parentJob = launch {
245
launch {
246
delay(100)
247
throw RuntimeException("Child exception")
248
}
249
250
launch {
251
delay(200)
252
println("This won't execute - parent gets cancelled")
253
}
254
255
delay(300)
256
println("Parent won't reach here")
257
}
258
259
parentJob.join()
260
} catch (e: Exception) {
261
println("Caught propagated exception: ${e.message}")
262
}
263
}
264
265
// Exception handling with async
266
suspend fun asyncExceptionHandling() = coroutineScope {
267
val deferred1 = async {
268
delay(100)
269
throw RuntimeException("Async exception")
270
}
271
272
val deferred2 = async {
273
delay(200)
274
"Success result"
275
}
276
277
try {
278
val result1 = deferred1.await() // Exception thrown here
279
println("Won't reach: $result1")
280
} catch (e: Exception) {
281
println("Caught async exception: ${e.message}")
282
}
283
284
try {
285
val result2 = deferred2.await()
286
println("Result2: $result2") // This still works
287
} catch (e: Exception) {
288
println("Unexpected exception: ${e.message}")
289
}
290
}
291
292
// Non-cancellable cleanup
293
suspend fun nonCancellableCleanup() {
294
val resource = "Important Resource"
295
296
try {
297
// Simulate work that gets cancelled
298
repeat(10) { i ->
299
println("Working on iteration $i")
300
delay(100)
301
}
302
} finally {
303
// Ensure cleanup runs even if cancelled
304
withContext(NonCancellable) {
305
println("Cleaning up $resource")
306
delay(50) // Even suspending cleanup operations work
307
println("Cleanup completed")
308
}
309
}
310
}
311
312
fun main() = runBlocking {
313
println("Regular job exception propagation:")
314
regularJobExceptionPropagation()
315
316
println("\nAsync exception handling:")
317
asyncExceptionHandling()
318
319
println("\nNon-cancellable cleanup:")
320
val job = launch {
321
nonCancellableCleanup()
322
}
323
324
delay(250)
325
job.cancel()
326
job.join()
327
}
328
```
329
330
### Error Recovery Patterns
331
332
Common patterns for error recovery and resilience.
333
334
```kotlin { .api }
335
/** Retry with exponential backoff */
336
suspend fun <T> retry(
337
retries: Int,
338
initialDelayMs: Long = 1000,
339
factor: Double = 2.0,
340
block: suspend () -> T
341
): T
342
343
/** Circuit breaker pattern */
344
class CircuitBreaker(
345
val failureThreshold: Int,
346
val recoveryTimeoutMs: Long
347
)
348
```
349
350
**Usage Examples:**
351
352
```kotlin
353
import kotlinx.coroutines.*
354
import kotlin.random.Random
355
356
// Retry with exponential backoff
357
suspend fun <T> retryWithBackoff(
358
retries: Int,
359
initialDelay: Long = 1000,
360
factor: Double = 2.0,
361
block: suspend () -> T
362
): T {
363
var currentDelay = initialDelay
364
repeat(retries) { attempt ->
365
try {
366
return block()
367
} catch (e: Exception) {
368
if (attempt == retries - 1) throw e
369
370
println("Attempt ${attempt + 1} failed: ${e.message}. Retrying in ${currentDelay}ms")
371
delay(currentDelay)
372
currentDelay = (currentDelay * factor).toLong()
373
}
374
}
375
error("Should not reach here")
376
}
377
378
// Circuit breaker implementation
379
class CircuitBreaker(
380
private val failureThreshold: Int,
381
private val recoveryTimeoutMs: Long
382
) {
383
private var failureCount = 0
384
private var lastFailureTime = 0L
385
private var state = State.CLOSED
386
387
enum class State { CLOSED, OPEN, HALF_OPEN }
388
389
suspend fun <T> execute(block: suspend () -> T): T {
390
when (state) {
391
State.CLOSED -> {
392
return try {
393
val result = block()
394
failureCount = 0
395
result
396
} catch (e: Exception) {
397
failureCount++
398
if (failureCount >= failureThreshold) {
399
state = State.OPEN
400
lastFailureTime = System.currentTimeMillis()
401
}
402
throw e
403
}
404
}
405
406
State.OPEN -> {
407
if (System.currentTimeMillis() - lastFailureTime >= recoveryTimeoutMs) {
408
state = State.HALF_OPEN
409
return execute(block)
410
} else {
411
throw RuntimeException("Circuit breaker is OPEN")
412
}
413
}
414
415
State.HALF_OPEN -> {
416
return try {
417
val result = block()
418
state = State.CLOSED
419
failureCount = 0
420
result
421
} catch (e: Exception) {
422
state = State.OPEN
423
lastFailureTime = System.currentTimeMillis()
424
throw e
425
}
426
}
427
}
428
}
429
}
430
431
// Fallback pattern
432
suspend fun <T> withFallback(
433
primary: suspend () -> T,
434
fallback: suspend () -> T
435
): T {
436
return try {
437
primary()
438
} catch (e: Exception) {
439
println("Primary failed: ${e.message}, using fallback")
440
fallback()
441
}
442
}
443
444
fun main() = runBlocking {
445
// Retry example
446
try {
447
val result = retryWithBackoff(retries = 3) {
448
if (Random.nextFloat() < 0.7) {
449
throw RuntimeException("Random failure")
450
}
451
"Success!"
452
}
453
println("Retry result: $result")
454
} catch (e: Exception) {
455
println("All retry attempts failed: ${e.message}")
456
}
457
458
// Circuit breaker example
459
val circuitBreaker = CircuitBreaker(failureThreshold = 3, recoveryTimeoutMs = 1000)
460
461
repeat(10) { i ->
462
try {
463
val result = circuitBreaker.execute {
464
if (i < 5 && Random.nextFloat() < 0.8) {
465
throw RuntimeException("Service failure")
466
}
467
"Service response $i"
468
}
469
println("Circuit breaker result: $result")
470
} catch (e: Exception) {
471
println("Circuit breaker blocked: ${e.message}")
472
}
473
474
delay(200)
475
}
476
477
// Fallback example
478
val result = withFallback(
479
primary = {
480
if (Random.nextBoolean()) throw RuntimeException("Primary service down")
481
"Primary result"
482
},
483
fallback = {
484
"Fallback result"
485
}
486
)
487
println("Fallback result: $result")
488
}
489
```
490
491
## Types
492
493
### Exception Types
494
495
Core exception types for coroutine error handling.
496
497
```kotlin { .api }
498
/** Base exception for cancellation */
499
open class CancellationException : IllegalStateException
500
501
/** Exception for timeout operations */
502
class TimeoutCancellationException : CancellationException
503
504
/** Exception for job cancellation */
505
class JobCancellationException : CancellationException
506
```
507
508
### Context Elements
509
510
Context elements related to exception handling.
511
512
```kotlin { .api }
513
/** Key for CoroutineExceptionHandler in context */
514
object CoroutineExceptionHandler : CoroutineContext.Key<CoroutineExceptionHandler>
515
516
/** Non-cancellable context element */
517
object NonCancellable : AbstractCoroutineContextElement(Job), Job
518
```