What does the `catch { }` operator do in a Kotlin Flow, and which exceptions can it handle?
answer
- Intermediate operator, upstream-only
- Lambda receives Throwable (`it`)
- Can emit/emitAll a fallback
- Does NOT catch the collector body
- Place just above collect
basics
~10 scatch runs when something fails earlier in the flow (upstream). It only catches errors from the parts above it, lets you log them, and can send a backup value instead of crashing.
solid answer
~40 s`catch { }` is an intermediate Flow operator that intercepts exceptions thrown by the upstream — the flow builder and all operators declared *above* it. Inside its lambda the thrown value is bound to `it` (type `Throwable`). It cannot see exceptions from operators *below* it, including the terminal `collect`. From the lambda you may log, swallow, rethrow, or call `emit(...)` / `emitAll(...)` to provide a fallback value so collection continues normally. Errors from downstream code (e.g. the collector body) are NOT caught here — that is the exception-transparency rule. Typical use: put `catch` just above `collect` to handle a failing network/database flow and emit a default.
code
kotlin · 8 linesflow {
emit("a")
throw IllegalStateException("fail")
}
.catch { e -> emit("fallback:${e.message}") }
.collect { println(it) }
// a
// fallback:failgo deeper
Knows catch handles upstream errors and can emit a fallback.
Explains upstream-only scope and that the collector body is excluded.
Ties it to exception transparency and explains why downstream is intentionally excluded.
Frames placement of catch as a design choice and discusses fallback vs propagate strategies for production flows.
## What `catch` is `catch { }` is an *intermediate operator* on `Flow`. Intermediate means it sits in the chain between the source (the `flow { }` builder or `flowOf(...)`) and the *terminal* operator (`collect`, `toList`, `first`, ...). It returns a new `Flow` and does nothing until collected. ## Upstream vs downstream - **Upstream** = everything declared *before* (above) `catch` in the chain. - **Downstream** = everything *after* it, including the terminal `collect` and the collector's lambda body. `catch` only sees exceptions that come from **upstream**. This is deliberate. ```kotlin flow { emit(1) throw RuntimeException("boom") // upstream failure } .catch { e -> println("caught: ${e.message}"); emit(-1) } // sees boom, emits fallback .collect { println(it) } // prints: 1, caught: boom, -1 ``` ## What you can do inside `catch` The lambda receives the `Throwable` as `it` (or a named param). You can: - **Log / swallow** it (do nothing else) — the flow then completes normally. - **`emit(value)` or `emitAll(otherFlow)`** — emit one or more *fallback* values downstream. - **Rethrow** (`throw it`) or throw a different exception to propagate failure. ```kotlin dataFlow .catch { emit(emptyList()) } // fallback to empty list on any upstream error .collect { render(it) } ``` ## What it does NOT catch It does **not** catch exceptions thrown in the collector or in any operator below it: ```kotlin flowOf(1, 2) .catch { println("never here") } .collect { throw RuntimeException("in collector") } // propagates to caller, NOT to catch ``` This is **exception transparency**: a `catch` only ever reacts to upstream emissions failing, never to how downstream consumes them. ## Key APIs - `Flow.catch(action: suspend FlowCollector<T>.(Throwable) -> Unit)` — the operator. - Inside it `emit` / `emitAll` are available because the receiver is a `FlowCollector<T>`.
- If you remove the `catch` and the upstream throws, what happens?The exception propagates down to the terminal operator and is thrown from the `collect` call, so the coroutine collecting it fails unless wrapped in try/catch.
- Can `catch` re-emit multiple values?Yes — its receiver is a `FlowCollector`, so you can call `emit` several times or `emitAll(anotherFlow)`.
Like a safety net strung under a tightrope: it only catches falls that happen above it, not the people walking on the ground below.
saying these in an interview costs you the question
- Saying `catch` catches errors from the collector / downstream
- Claiming `catch` is a terminal operator
- Thinking you can't emit fallbacks from `catch`
- Confusing it with a plain try/catch wrapping the whole chain