What is CoroutineExceptionHandler in Kotlin coroutines, and what kind of failures does it handle?
answer
- Last-resort catcher for uncaught launch exceptions
- It's a CoroutineContext element with its own Key
- launch yes, async no (Deferred re-throws at await)
- Handles, never recovers/resumes
- Ignores CancellationException
basics
~10 sIt is a piece of coroutine context that acts as a last-resort catcher. When a coroutine started with launch fails and nobody else handles the exception, this handler runs instead of crashing silently.
solid answer
~40 sCoroutineExceptionHandler is a CoroutineContext element (a key/value entry in the context) used as a last-resort handler for uncaught exceptions. You add it to a scope or to launch via the context parameter: launch(handler) { ... }. When a launch coroutine throws and the exception is not caught by user code and reaches a root coroutine, the runtime invokes handler.handleException(context, throwable) instead of crashing the thread (or, on the JVM, calling the default uncaught handler). It only catches; it cannot resume the coroutine. It is invoked for launch (a 'fire and forget' builder whose result nobody awaits). It is NOT consulted for exceptions from async, because those are stored in the Deferred and re-thrown at await().
code
kotlin · 5 linesval handler = CoroutineExceptionHandler { _, e ->
println("caught: ${e.message}")
}
val scope = CoroutineScope(SupervisorJob() + handler)
scope.launch { throw IllegalStateException("oops") } // -> caught: oopsgo deeper
Knows it is a context element that catches uncaught launch exceptions and that it cannot recover the coroutine.
Explains 'uncaught' precisely (reaches a root coroutine), and that async exceptions go through await instead.
Discusses installation point (scope/root vs child), interplay with Job/SupervisorJob, and CancellationException being ignored.
Frames it as a last-resort policy hook (logging/alerting) versus structured handling, and reasons about where it belongs in an app's coroutine architecture.
## What it is `CoroutineExceptionHandler` is an element of the **coroutine context** — the same map-like structure that also holds the `Job`, the `CoroutineDispatcher`, and the `CoroutineName`. Each element has a `Key`; `CoroutineExceptionHandler` is both the interface and its own key. You create one with the factory function: ```kotlin val handler = CoroutineExceptionHandler { context, throwable -> println("Caught $throwable") } ``` ## How you install it You pass it as part of the context to a scope or a builder: ```kotlin val scope = CoroutineScope(SupervisorJob() + handler) scope.launch { throw RuntimeException("boom") } // handler runs ``` ## What 'uncaught' means An exception is **uncaught** when it propagates out of your coroutine body without a surrounding `try/catch` and travels up to a **root coroutine** (a coroutine directly in a scope, not a child of another coroutine). At that point the machinery has nowhere left to deliver it, so it calls `handler.handleException(context, throwable)`. Without a handler, the JVM falls back to the thread's default uncaught-exception handler (often printing a stack trace). ## It only works for launch `launch` produces a `Job` and its result is never awaited, so a thrown exception is genuinely 'uncaught' — the handler is the only place to observe it. `async` produces a `Deferred<T>`; the exception is **encapsulated** in that Deferred and rethrown when someone calls `await()`. Because the caller is expected to handle it there, the `CoroutineExceptionHandler` is **not** invoked for the async result. ## Key limits - It is a **handler, not a recovery point** — it cannot resume or retry the coroutine. - It must be installed at the **scope or root coroutine**; putting it on an inner `launch` that is a child of another coroutine has no effect (the child delegates handling to its parent). - `CancellationException` is ignored — cancellation is normal control flow, not an error. Key APIs/keywords: `CoroutineExceptionHandler`, `handleException`, `CoroutineContext`, `Job`, `launch`, `async`, `Deferred.await`, `SupervisorJob`, `CancellationException`.
- Why does the handler not fire for an exception thrown inside async?Because async stores the exception in the returned Deferred and rethrows it at await(); the caller is expected to handle it there, so it is not treated as uncaught.
It is like a smoke detector at the front door: it only alerts once the failure has escaped every room (no try/catch caught it), and it can warn you but not put out the fire.
saying these in an interview costs you the question
- Claiming it works the same for launch and async
- Saying it can resume or retry the failed coroutine
- Confusing it with a regular try/catch inside the body
- Thinking it catches CancellationException