Error handling in coroutines.
Concurrent code fails. Network calls time out. Computations throw exceptions. How do you handle errors when coroutines are involved? Today we learn the patterns for error handling in coroutine-based code.
Coroutines can throw exceptions just like regular code.
An exception in a coroutine propagates up the coroutine hierarchy. Child fails, parent is notified, siblings are cancelled.
This is structured concurrency in action. Failure propagates. Related work stops.
Unhandled exceptions crash the coroutine. In some contexts, they crash the entire scope. In Android, unhandled exceptions on Main crash the app.
You must handle exceptions. Ignore them, and your app fails.
The simplest approach: try-catch.
Wrap the code that might fail in a try block. Catch the exception. Handle it.
suspend fun fetchData try network call catch handle error. This works exactly like regular code.
Inside a coroutine, try-catch works normally. You can catch exceptions from suspend function calls.
Be specific about what you catch. Catch IOException for network errors. Do not catch CancellationException, that breaks cancellation.
try-catch is local. It handles errors at the point where they occur.
CancellationException is special.
When a coroutine is cancelled, it throws CancellationException. This is how cancellation works.
If you catch CancellationException and do not rethrow it, you break cancellation. The coroutine does not cancel properly.
catch e Exception catches CancellationException too. Dangerous.
Safe pattern: catch e Exception if e is CancellationException throw e else handle.
Or be specific. Catch only the exceptions you expect. Let others propagate.
CoroutineExceptionHandler handles uncaught exceptions.
val handler equals CoroutineExceptionHandler brace context comma exception arrow log exception close brace.
Add it to the coroutine context: launch handler plus Dispatchers dot IO brace work close brace.
The handler catches exceptions that would otherwise crash. It is a last resort, not a replacement for try-catch.
Important: CoroutineExceptionHandler only works with launch, not async. async stores exceptions in the Deferred. You get them when you call await.
Use handlers for logging and cleanup when exceptions escape all try-catch blocks.
async behaves differently from launch.
launch propagates exceptions immediately. async stores the exception.
The exception surfaces when you call await. await throws the exception.
try val result equals deferred dot await catch handle. This is where you catch async errors.
If you never call await, the exception may be lost. Always await your async results.
You can also check deferred dot isFailed without throwing by using getCompleted with checks.
By default, child failure cancels siblings. Sometimes you want isolation.
SupervisorJob changes the behavior. Child failures do not propagate to siblings.
val supervisor equals SupervisorJob. Use it in your scope.
With SupervisorJob, each child fails independently. One child throws, others continue.
supervisorScope creates a scope with this behavior. Children are isolated.
Use supervision when children are independent. Processing items in parallel where one failure should not stop others.
Flows have their own error handling.
The catch operator handles exceptions from upstream.
flow dot map might throw dot catch brace e arrow emit fallback close brace dot collect.
catch receives the exception. You can emit fallback values. You can log. You can rethrow.
catch only catches exceptions from operators before it in the chain. Exceptions in collect are not caught.
For collect errors, wrap the entire collect in try-catch.
onCompletion runs when the flow completes, successfully or not.
flow dot onCompletion brace cause arrow if cause not null log error close brace.
If there was an exception, cause is not null. If flow completed normally, cause is null.
Use onCompletion for cleanup, logging, or UI updates that should happen regardless of success.
onCompletion does not suppress exceptions. They still propagate after onCompletion runs.
Let us summarize.
Exceptions in coroutines propagate through the hierarchy.
Use try-catch for local error handling. Avoid catching CancellationException.
CoroutineExceptionHandler handles uncaught exceptions at the scope level.
SupervisorJob isolates child failures.
Flow uses catch for upstream errors and onCompletion for cleanup.
Handle errors deliberately. Unhandled exceptions crash your application.