Advanced Exception Handling in Kotlin Coroutines: A Guide for Android Developers | by Dobri Kostadinov | Feb, 2025

Advanced Exception Handling in Kotlin Coroutines: A Guide for Android Developers | by Dobri Kostadinov | Feb, 2025

Exception handling in Kotlin Coroutines is often misunderstood, especially when dealing with structured concurrency, exception propagation, and parallel execution. A poorly handled coroutine failure can crash your Android app or lead to silent failures, making debugging difficult.

In this article, we will cover advanced scenarios of exception handling, including:
✅ How exceptions propagate in coroutine hierarchies
✅ Handling exceptions in async, launch, and supervisorScope
✅ Managing errors in Flow, SharedFlow, and StateFlow
✅ Retrying failed operations with exponential backoff
✅ Best practices for handling exceptions in ViewModel and WorkManager

By the end of this guide, you’ll be confidently handling coroutine failures in any Android project. 🚀

Kotlin coroutines follow structured concurrency, meaning that when a parent coroutine is canceled, all of its children are also canceled. Likewise, when a child coroutine fails, the failure propagates up the hierarchy, canceling the entire coroutine scope.

Example: How a Failure in a Child Cancels the Entire Scope

val scope = CoroutineScope(Job())

scope.launch {
launch {
delay(500)
throw IllegalArgumentException("Child coroutine failed")
}
delay(1000)
println("This line will never execute")
}

What happens?

  • The child coroutine throws an exception.
  • The parent scope is canceled, and no other coroutines in the scope continue execution.

Solution: Use supervisorScope to Prevent Propagation

To prevent failures from canceling all coroutines, wrap them inside a supervisorScope:

scope.launch {
supervisorScope {
launch {
delay(500)
throw IllegalArgumentException("Child coroutine failed")
}
delay(1000)
println("This line will still execute")
}
}

📌 Key Takeaway: Use supervisorScope when you want sibling coroutines to run independently, even if one fails.

How launch and async Handle Exceptions

  • launch {} immediately cancels the parent scope if an exception is thrown.
  • async {} delays exception propagation until await() is called.

Example: launch Cancels Everything on Failure

scope.launch {
launch {
throw IOException("Network error")
}
delay(1000) // This will never execute
}

Example: async Hides the Exception Until await()

val deferred = scope.async {
throw NullPointerException("Async failed")
}
deferred.await() // Exception is thrown here

🚨 Danger: If you forget to call await(), the exception is silently ignored.

Solution: Wrap await() Calls in Try-Catch

try {
val result = deferred.await()
} catch (e: Exception) {
Log.e("Coroutine", "Handled exception: $e")
}

📌 Key Takeaway: Always wrap await() in a try-catch block to prevent unhandled exceptions.

In Android development, viewModelScope is used to launch coroutines in ViewModels. However, uncaught exceptions in viewModelScope crash the app unless properly handled.

Example: Crashing ViewModel Without Handling

class MyViewModel : ViewModel() {
fun fetchData() {
viewModelScope.launch {
throw IOException("Network failure")
}
}
}

📌 Problem: The exception is uncaught and crashes the app.

Solution: Use CoroutineExceptionHandler

class MyViewModel : ViewModel() {
private val handler = CoroutineExceptionHandler { _, throwable ->
Log.e("Coroutine", "Caught: $throwable")
}

fun fetchData() {
viewModelScope.launch(handler) {
throw IOException("Network failure")
}
}
}

📌 Key Takeaway: Always attach a CoroutineExceptionHandler to prevent crashes.

When executing multiple tasks in parallel, one coroutine failing cancels the others.

Example: One Failing Coroutine Cancels the Other

val result1 = async { fetchUserData() }
val result2 = async { fetchPosts() }
val userData = result1.await() // If this fails, result2 is also canceled
val posts = result2.await()

📌 Problem: If fetchUserData() fails, fetchPosts() is also canceled.

Solution: Use supervisorScope to Make Coroutines Independent

supervisorScope {
val userData = async { fetchUserData() }
val posts = async { fetchPosts() }

try {
userData.await()
posts.await()
} catch (e: Exception) {
Log.e("Coroutine", "One coroutine failed, but the other continued")
}
}

📌 Key Takeaway: supervisorScope ensures one failure does not cancel everything.

Flows stop execution if an exception occurs inside collect().

Example: Flow Crashes on Exception

flow {
emit(1)
throw IllegalStateException("Error in flow")
}.collect {
println(it) // This stops execution after first emit
}

Solution: Use catch {} to Handle Flow Exceptions

flow {
emit(1)
throw IllegalStateException("Error in flow")
}
.catch { e -> Log.e("Flow", "Caught exception: $e") }
.collect { println(it) }

📌 Key Takeaway: Always use .catch {} to handle errors inside a Flow.

If a coroutine fails due to a network error, we can retry with exponential backoff.

suspend fun fetchDataWithRetry(): String {
var attempt = 0
val maxAttempts = 3
while (attempt try {
return fetchUserData()
} catch (e: IOException) {
attempt++
delay(1000L * attempt) // Exponential backoff
}
}
throw IOException("Failed after 3 attempts")
}

📌 Key Takeaway: Implement retries with increasing delay to handle transient failures.

When using WorkManager with coroutines, exceptions inside workers do not automatically retry.

Example: A Worker That Fails Silently

class MyWorker(ctx: Context, params: WorkerParameters) :
CoroutineWorker(ctx, params) {
override suspend fun doWork(): Result {
fetchData() // May fail
return Result.success()
}
}

📌 Problem: If fetchData() fails, WorkManager does not retry.

Solution: Return Result.retry() on Exception

override suspend fun doWork(): Result {
return try {
fetchData()
Result.success()
} catch (e: Exception) {
Result.retry() // Automatically retries on failure
}
}

📌 Key Takeaway: Use Result.retry() to ensure automatic retries.

Mastering exception handling in coroutines ensures that your app is resilient, fault-tolerant, and reliable.

What tricky coroutine failures have you encountered? Let me know in the comments! 🚀

Dobri Kostadinov
Android Consultant | Trainer
Email me | Follow me on LinkedIn | Follow me on Medium | Buy me a coffee

About sujan

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.