skip to content

When should you use the `catch { }` operator versus wrapping the `collect` call in a plain `try/catch`? What does each cover?

level: seniorimportance: should knowfreq 38%

answer

  1. catch = upstream only; try/catch around collect = everything
  2. catch can emit fallbacks; outer try cannot
  3. collector-body errors need the outer try/catch
  4. Combine: catch upstream + try/catch terminal
  5. Rethrow CancellationException in the outer catch

basics

~10 s

Use catch for errors coming from the flow itself (upstream). Use a try/catch around collect when you also need to handle errors from your collector code. They cover different parts.

solid answer

~40 s

`catch { }` only intercepts **upstream** exceptions (builder + operators above it) and never the collector body — that's exception transparency. A `try/catch` placed *around the whole `collect` call* covers everything the collection raises: upstream errors that reached the terminal operator AND exceptions thrown inside the collector lambda. So they're complementary. Prefer `catch` when you want to recover or emit fallbacks while keeping the stream going and keep producer-error handling declarative and transparency-safe. Use an outer `try/catch` around `collect` when you must also handle collector-side failures (e.g. rendering throws) or want one boundary for the whole consumption. A frequent idiom: `catch` to recover upstream + an outer try/catch (or `runCatching`) around `collect` to handle terminal/collector failures separately.

code

kotlin · 8 lines
kotlin
try {
    source
        .catch { emit(Default) }      // upstream recovery, transparency-safe
        .collect { sink.write(it) }    // write() failure -> outer catch
} catch (e: Throwable) {
    if (e is CancellationException) throw e
    log.error("consume failed", e)
}

go deeper

for a junior

Knows catch is for flow errors and try/catch wraps the collect call.

for a middle

States the upstream vs collector-body coverage difference clearly.

for a senior

Combines both with correct separation of concerns and cancellation handling.

for a principal

Establishes a consumption-boundary convention across the codebase and reasons about where recovery vs failure surfaces belong.

## Two different scopes | Mechanism | Catches upstream errors? | Catches collector-body errors? | Can emit fallbacks? | |---|---|---|---| | `catch { }` operator | Yes (above it only) | **No** | Yes (`emit`/`emitAll`) | | `try/catch` around `collect` | Yes (they surface at the terminal) | **Yes** | No (you're outside the flow) | ## Why the difference exists `catch` is upstream-only by design (exception transparency). The collector body's exceptions are downstream, so `catch` can't see them. But once they propagate out of `collect`, an enclosing `try/catch` can. ```kotlin // catch: upstream recovery, transparency-safe upstream .catch { emit(fallback) } // handles producer failure .collect { render(it) } // a throw HERE is not caught by catch ``` ```kotlin // try/catch around collect: covers collector body too try { upstream.collect { render(it) } // render() may throw } catch (e: Throwable) { if (e is CancellationException) throw e showError(e) } ``` ## Combine them Use both for clear separation of concerns: ```kotlin try { repo.stream() .catch { emit(emptyList()) } // recover producer errors, keep streaming .collect { ui.render(it) } // collector failures fall through to the try } catch (e: Throwable) { if (e is CancellationException) throw e ui.showFatal(e) } ``` ## Decision guide - Need a **fallback value** and keep emitting → `catch`. - Need to handle **collector/render** failures → outer try/catch. - Want declarative, transparency-safe producer handling → `catch`. - Want a single consumption boundary including downstream → try/catch around `collect`. ## Key APIs / keywords - `catch { }` (upstream), `emit`, `emitAll`. - terminal `collect`; `try/catch`; `runCatching` as a variant. - Remember CancellationException rethrow in the outer catch too.

  • Can a `try/catch` around `collect` emit a fallback into the flow?
    No — by then you're outside the flow's collector context; there's no `FlowCollector` to emit into. Only `catch { }` can emit fallbacks.
  • If the collector throws, will an upstream `catch` ever see it?
    No. Exception transparency keeps downstream (collector) exceptions out of `catch`; they only surface to an enclosing try/catch around `collect`.

saying these in an interview costs you the question

  • Expecting `catch` to handle collector-body exceptions
  • Believing try/catch around collect can emit fallbacks into the flow
  • Using only one mechanism when both concerns exist
  • Forgetting CancellationException in the outer catch

context