What goes wrong if you catch a broad exception type around a suspending call and swallow it? How should you handle CancellationException?
answer
- Never swallow CancellationException
- catch (e: CancellationException) { throw e } first
- it extends IllegalStateException
- ensureActive() re-checks after broad catch
- swallow = cancellation silently dies
basics
~10 sCancellationException is how a coroutine learns it was cancelled. If you catch it and don't rethrow, the coroutine thinks the error was handled and keeps running, so cancellation silently stops working.
solid answer
~40 sCooperative cancellation works by throwing CancellationException at a suspension point. A broad catch (e: Exception) or catch (e: Throwable) that swallows everything will also catch CancellationException; if you don't rethrow it, you defeat cancellation — the coroutine continues as if nothing happened and may run forever. The rule: never swallow CancellationException. Either don't catch it, or catch it, run minimal cleanup, and rethrow. A common pattern is to rethrow it explicitly: catch (e: CancellationException) { throw e } before a generic handler, or use currentCoroutineContext().ensureActive() after a broad catch. Note CancellationException extends IllegalStateException, so naive type checks can miss it. Cleanup that itself must suspend should run under withContext(NonCancellable) (that mechanism is its own topic).
code
kotlin · 7 linestry {
process()
} catch (e: CancellationException) {
throw e // must rethrow
} catch (e: Exception) {
log.error("real failure", e)
}go deeper
Knows you shouldn't swallow CancellationException and that delay() can throw it.
Explains the swallow-defeats-cancellation mechanism and the rethrow-first pattern; knows the IllegalStateException inheritance.
Designs robust try/catch around suspending code, uses ensureActive() and NonCancellable cleanup correctly.
Establishes team conventions/lint rules to prevent CancellationException swallowing across a codebase.
## The trap Cooperative cancellation is delivered as a thrown `CancellationException`. Exception handling code can accidentally **eat** it: ```kotlin val job = launch { try { while (true) { doWork() delay(100) // throws CancellationException when cancelled } } catch (e: Exception) { // <-- also catches CancellationException! log.warn("ignored", e) // swallowed -> cancellation defeated } } job.cancelAndJoin() // never returns control as expected; loop continues ``` Because the `catch (e: Exception)` block handles the `CancellationException` and does not rethrow, the coroutine believes the failure was dealt with and **keeps looping**. Cancellation silently stops working. ## Why it's easy to miss `kotlinx.coroutines.CancellationException` (on JVM, a typealias to `java.util.concurrent.CancellationException`) extends `IllegalStateException`, which extends `RuntimeException`/`Exception`. So `catch (e: Exception)`, `catch (e: RuntimeException)`, and even `catch (e: IllegalStateException)` all capture it. ## Correct patterns **1. Rethrow CancellationException first:** ```kotlin try { riskyWork() } catch (e: CancellationException) { throw e // let cancellation propagate } catch (e: Exception) { handle(e) // real errors only } ``` **2. Re-assert active state after a broad catch:** ```kotlin try { riskyWork() } catch (e: Exception) { recordError(e) } currentCoroutineContext().ensureActive() // throws again if cancelled ``` **3. Don't catch at all** unless you have a specific reason. ## Cleanup on cancellation It's fine to catch `CancellationException` to run **cleanup**, as long as you rethrow: ```kotlin try { stream() } catch (e: CancellationException) { closeQuietly(); throw e } ``` If the cleanup itself needs to call suspend functions, the surrounding coroutine is already cancelled, so those calls would immediately throw. Running them under `withContext(NonCancellable) { ... }` lets cleanup complete — that's a sibling topic, but worth naming. ## Summary rule **Never swallow `CancellationException`.** Catch narrowly, rethrow cancellation, and reserve broad catches for genuine failures.
- Why is catch (e: IllegalStateException) dangerous around suspend calls?CancellationException extends IllegalStateException, so such a catch can swallow cancellation unless it rethrows it.
- How can you recover cancellation after an unavoidable broad catch?Call currentCoroutineContext().ensureActive() (or check isActive) afterwards; it rethrows CancellationException if the job was cancelled.
It's like intercepting a 'stop' order, signing for it, and then continuing anyway — the sender thinks it was obeyed but the worker never stops.
saying these in an interview costs you the question
- catch (e: Exception) {} around suspend code with no rethrow
- Doesn't know CancellationException subclasses IllegalStateException
- Logs and swallows cancellation as a normal error
- Treats CancellationException as a bug to suppress rather than a signal