avoid_empty_catch
Flags a catch (or on) clause whose body is empty. The failure is caught and then discarded, so nothing downstream can tell it happened.
How this differs from the SDK’s empty_catches
Section titled “How this differs from the SDK’s empty_catches”Dart ships empty_catches, which permits the two shapes that hide the most:
| Code | empty_catches |
avoid_empty_catch |
|---|---|---|
catch (e) {} |
reports | reports |
catch (_) {} |
allows | reports |
catch (e) { /* ignored */ } |
allows | reports by default |
catch (e) { log(e); } |
allows | allows |
There is no need to enable both: this rule reports everything empty_catches does. Set allow_with_comment: true to restore the SDK’s policy for the commented form.
See also: Dart lint: empty_catches | Error handling
Future<void> publish(String path) async { try { await uploadSymbols(path); } catch (_) {}}
Future<void> uploadSymbols(String path) async {}Do something observable with the failure. Which of these fits depends on whether the caller can recover.
Log it and carry on, when the operation is genuinely optional:
Future<void> publish(String path) async { try { await uploadSymbols(path); } on UploadFailure catch (e, s) { log.warning('symbol upload failed', e, s); }}
Future<void> uploadSymbols(String path) async {}
class UploadFailure implements Exception {}
abstract final class log { static void warning(String message, Object error, StackTrace stack) {}}Or return a fallback the caller can act on:
Config readConfig(String source) { try { return parseConfig(source); } on FormatException { return Config.defaults; }}
class Config { static const defaults = Config();
const Config();}
Config parseConfig(String source) => const Config();A typed on clause with an empty body is the same defect
Section titled “A typed on clause with an empty body is the same defect”Narrowing the clause does not make the silence deliberate — it just narrows what gets silenced:
void demo(String source) { // Don't try { parseConfig(source); } on FormatException {}}
void parseConfig(String source) {}A comment is not a handler
Section titled “A comment is not a handler”By default the rule reports a body holding nothing but a comment, because the comment reaches a reader of this line and nothing else:
void demo() { // Don't try { flushCache(); } catch (e) { // Best effort. }}
void flushCache() {}If your project treats that comment as the deliberate marker, turn it into one:
many_lints: rules: avoid_empty_catch: allow_with_comment: truerules: avoid_empty_catch: allow_with_comment: trueWith that set, the block above is accepted and the bare catch (_) {} still reports — so the ignores in your codebase become explicit and greppable.
Known limitations
Section titled “Known limitations”A body is “empty” when it holds no statements. A body containing anything at all — even null; — is accepted, so the rule cannot tell a real handler from a token one.
Each empty clause on a try is reported separately; a try with two empty on clauses produces two diagnostics.
There is no quick fix: what belongs in the body is the decision the rule is asking you to make.
Options
Section titled “Options”| Option | Type | Default | Description |
|---|---|---|---|
allow_with_comment |
bool | false |
Accept a catch whose body holds a comment, matching the SDK’s empty_catches policy |
Turning this rule off
Section titled “Turning this rule off”This rule is in the recommended preset, so it is on with
preset: recommended, preset: opinionated or preset: pedantic. Add it to
preset: core with avoid_empty_catch: true.
To turn it off:
rules: avoid_empty_catch: falseTo keep the rule on but skip certain paths, use per-rule exclude.
Related rules
Section titled “Related rules”avoid_throw_in_catch_block— Detect throw expressions inside catch blocks.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.