Skip to content

prefer_typed_exceptions

v1.1.0WarningConfigurableControl Flow

Warns when a throw does not name a type a caller could catch selectively — a bare Exception('...'), a raw string, or any object that is neither Error nor Exception.

throw Exception('upload failed') cannot be caught by kind. Every catch downstream either swallows everything or re-inspects the message string, and message matching breaks the moment somebody improves the wording. That break is silent: the catch still compiles and still runs, it just stops matching.

This is concrete for anything that maps failures onto behaviour — a CLI choosing an exit code, a client deciding whether to retry, a UI choosing between “check your connection” and “that file is not valid”. None of those questions can be answered about a value whose only distinguishing feature is prose.

How this differs from the SDK’s only_throw_errors

Section titled “How this differs from the SDK’s only_throw_errors”

Dart’s only_throw_errors covers the adjacent case — throwing something that is neither Error nor Exception, like a bare string — but is satisfied by Exception(...), which is where most of the real damage is. This rule covers both, so enabling it alone is sufficient.

See also: Dart lint: only_throw_errors | Effective Dart: error handling

void bad() {
// Nothing downstream can catch just this failure.
throw Exception('upload failed');
// Not even an Exception.
throw 'upload failed';
// A domain object that forgot to implement Exception is just as opaque.
throw Failure('upload failed');
}
class UploadFailure implements Exception {
const UploadFailure(this.message);
final String message;
}
void good() {
// A caller can catch exactly this, and nothing else.
throw const UploadFailure('upload failed');
}
void alsoGood(int port) {
// SDK types whose own name identifies the failure are allowed by default.
throw ArgumentError.value(port, 'port', 'must be positive');
}

Catching then becomes precise, which is what makes an error taxonomy testable:

try {
await upload(artifact);
} on AuthFailure {
exitCode = 3;
} on UploadFailure {
exitCode = 4;
}

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 prefer_typed_exceptions: true.

To turn it off:

many_lints.yaml
rules:
prefer_typed_exceptions: false

To keep the rule on but skip certain paths — tests often throw freely — use per-rule exclude.

analysis_options.yaml
many_lints:
rules:
prefer_typed_exceptions:
additional_allow:
- TimeoutException
Option Type Default Description
allow list of strings see below SDK types specific enough to throw directly. Replaces the default list
additional_allow list of strings [] Adds to whichever list won, so you can extend the defaults without restating them

The default allow list is:

ArgumentError, AssertionError, ConcurrentModificationError, FormatException, IndexError, RangeError, StateError, UnimplementedError, UnsupportedError.

Each of these is already as catchable as a hand-written class would be, so wrapping one adds a type without adding information. Prefer additional_allow over allow: a restated default list silently rots when a later version of this package adds a name.