avoid_throw_in_catch_block
v0.4.0 Warning Fix Control Flow
Flags a throw inside a catch block. The new exception starts a fresh stack trace at the throw, so the report points at the error handler instead of at the line that actually failed. Use rethrow, or Error.throwWithStackTrace when you want to change the exception type.
See also: Exceptions | Dart lint: throw_in_finally | Dart lint: only_throw_errors
Translating a low-level failure into a domain exception is the right instinct — but a bare throw throws away the only thing that says where it came from:
Future<Order> loadOrder(String id) async { try { return await fetchOrder(id); } on Object { throw OrderUnavailable(id); }}
Future<Order> fetchOrder(String id) async => Order();
class Order {}
class OrderUnavailable implements Exception { OrderUnavailable(this.id);
final String id;}Keep the wrapper and the trace by passing the caught stack explicitly:
Future<Order> loadOrder(String id) async { try { return await fetchOrder(id); } catch (error, stack) { Error.throwWithStackTrace(OrderUnavailable(id), stack); }}
Future<Order> fetchOrder(String id) async => Order();
class Order {}
class OrderUnavailable implements Exception { OrderUnavailable(this.id);
final String id;}Forwarding the same exception
Section titled “Forwarding the same exception”throw e re-throws the caught object but restarts the trace at the throw. rethrow keeps the original:
void demo(String id) { // Don't try { fetchOrderSync(id); } catch (e) { print(e); throw e; }}
void fetchOrderSync(String id) {}void demo(String id) { // Do try { fetchOrderSync(id); } catch (e) { print(e); rethrow; }}
void fetchOrderSync(String id) {}A throw inside a closure is fine
Section titled “A throw inside a closure is fine”The rule does not descend into nested functions, because a throw there runs whenever the closure is called — not while the catch block is unwinding:
void demo(String id) { try { fetchOrderSync(id); } catch (e) { scheduleRetry(() { throw StateError('retry gave up'); // Not reported }); }}
void fetchOrderSync(String id) {}
void scheduleRetry(void Function() action) {}Known limitations
Section titled “Known limitations”Every throw reachable in the catch body is reported, however deeply nested in if, for, or switch — but never one inside a closure or a local function declaration.
The rule looks at catch and on clauses only. A throw in a finally block is a separate problem, covered by the SDK’s throw_in_finally.
The quick fix rewrites throw X to Error.throwWithStackTrace(X, stackTrace), adding the stack-trace parameter to the clause when it does not already have one — catch (e) becomes catch (e, stackTrace), and a bare on FormatException { becomes on FormatException catch (_, stackTrace) {. When the thrown expression is the caught exception itself, plain rethrow is shorter and says the same thing; the fix does not choose it for you.
Configuration
Section titled “Configuration”This rule is in the recommended preset, so it is on with
preset: recommended or preset: opinionated. Add it to preset: core with
avoid_throw_in_catch_block: true.
To turn it off:
rules: avoid_throw_in_catch_block: falseTo keep the rule on but skip certain paths, use per-rule exclude.
Related rules
Section titled “Related rules”avoid_empty_catch— Detect catch clauses that silently discard the failure.avoid_cascade_after_if_null— Detect cascades after if-null operators without parentheses.avoid_collapsible_if— Merge nested if statements with &&.avoid_constant_conditions— Detect comparisons where both sides are constants.