Coroutine Exception Handling

Coroutine Exception Handling (Kotlin)

Handling exceptions in Kotlin Coroutines is different from normal try–catch, because coroutines are asynchronous and follow structured concurrency.

This guide explains all important rules, patterns, and best practices.


1. Basic Rule (Most Important)

Exceptions in coroutines are propagated to the parent scope

  • Child coroutine fails → parent fails

  • Parent cancels all children


2. try–catch with launch (Won’t Work)

❌ This does NOT catch exceptions:

try {
launch {
throw Exception("Error")
}
} catch (e: Exception) {
println("Caught") // ❌ NOT executed
}

Reason:

  • launch is fire-and-forget

  • Exception happens later


3. Correct try–catch Inside Coroutine

✅ Catch inside the coroutine:

launch {
try {
throw Exception("Something went wrong")
} catch (e: Exception) {
println("Caught: ${e.message}")
}
}

4. async Exception Handling (Important Difference)

async stores exception until await() is called.

val deferred = async {
throw Exception("Failed")
}

try {
deferred.await()
} catch (e: Exception) {
println("Caught: ${e.message}")
}


5. CoroutineExceptionHandler

Used for uncaught exceptions in launch.

val handler = CoroutineExceptionHandler { _, exception ->
println("Handled: ${exception.message}")
}

val scope = CoroutineScope(Dispatchers.IO + handler)

scope.launch {
throw Exception("Crash")
}

Rules:

  • Works with launch

  • ❌ Does NOT work with async

  • Handles last-resort exceptions


6. supervisorScope (Prevent Cascade Failure)

By default:

  • One child fails → all siblings cancel

supervisorScope changes this behavior.

supervisorScope {
launch {
throw Exception("Child 1 failed")
}

launch {
delay(1000)
println("Child 2 still running")
}
}

✅ Child 2 continues running


7. SupervisorJob

Used at scope level.

val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)

scope.launch {
throw Exception("Fail")
}

scope.launch {
delay(1000)
println("Still alive")
}


8. Exception Handling in coroutineScope

try {
coroutineScope {
launch { throw Exception("Error") }
}
} catch (e: Exception) {
println("Caught at parent")
}

✔ Exception propagates to parent


9. CancellationException (Special Case)

launch {
throw CancellationException("Cancelled")
}
  • ❌ Not treated as error

  • ❌ Not caught by handler

  • Used internally for cancellation


10. Flow Exception Handling

❌ try–catch won’t work outside

try {
flow.collect { }
} catch (e: Exception) { }

✅ Use catch operator

flow {
emit(1)
throw Exception("Flow error")
}.catch {
emit(-1)
}.collect {
println(it)
}

11. Best Practices (Very Important)

✔ Use try–catch inside coroutine
✔ Use async + await() for result-based error handling
✔ Use CoroutineExceptionHandler for logging
✔ Use SupervisorJob in ViewModels
✔ Use Flow catch {} for streams


12. Android MVVM Example

viewModelScope.launch {
try {
repository.loadData()
} catch (e: Exception) {
_uiState.value = Error(e.message)
}
}

Summary Table

Scenario Solution
launch error try–catch inside
async error await + try–catch
Global error CoroutineExceptionHandler
Prevent cascade SupervisorJob
Flow error catch operator

You may also like...