avoid_non_null_assertion
This rule flags every use of the null-assertion operator !.
! is not a check — it is an unchecked assertion. It tells the compiler a value is non-null and throws a TypeError at runtime when that turns out to be wrong, at exactly the point the value is most likely to be null.
This rule is in the opinionated preset and works out of the box.
class Session { Session(this.user);
final User? user;
String greeting() => 'Welcome, ${user!.name}'; // throws when signed out}
class User { const User(this.name);
final String name;}Pick whichever of these matches what should happen when the value is null.
Short-circuit and carry the null forward:
class Session { Session(this.user);
final User? user;
String? greeting() => user == null ? null : 'Welcome, ${user!.name}';}Supply a fallback:
class Session { Session(this.user);
final User? user;
String greeting() => 'Welcome, ${user?.name ?? 'guest'}';}Or promote to a local, so the compiler proves it and no bang is needed:
class Session { Session(this.user);
final User? user;
String greeting() { final user = this.user; if (user == null) return 'Welcome, guest';
return 'Welcome, ${user.name}'; }}More examples
Section titled “More examples”Chained bangs
Section titled “Chained bangs”Each one is reported separately, and each can throw on its own:
class Config { const Config(this.timeout);
final Duration? timeout;}
// Two bangs, two crash sites.int seconds(Config? config) => config!.timeout!.inSeconds;int seconds(Config? config) => config?.timeout?.inSeconds ?? 0;A bang on a nullable return value
Section titled “A bang on a nullable return value”class Store { Account? find(String id) => null;}
class Account { void close() {}}
// Reported — `find` returns null for an unknown id, by design.void closeAccount(Store store, String id) => store.find(id)!.close();void closeAccount(Store store, String id) => store.find(id)?.close();Map lookups are exempt by default
Section titled “Map lookups are exempt by default”Map’s index operator is declared nullable no matter what the map holds, so
map[key]! is the idiomatic spelling for a key you know is there:
// Not reported with the default `ignore_map_indexes: true`.String shout(Map<String, String> translations) => translations['title']!.toUpperCase();The pedantic preset sets ignore_map_indexes: false, which bans every
postfix ! including this one. Set it yourself to get the same:
rules: avoid_non_null_assertion: ignore_map_indexes: falseBangs inside a null check
Section titled “Bangs inside a null check”A bang on a field inside if (field != null) is reported by default,
because promotion does not apply to fields — the bang is genuinely what makes
the code compile, and the field can change between the check and the use:
class Session { Session(this.user);
final User? user;
String greeting() { if (user != null) { return 'Welcome, ${user!.name}'; // still reported } return 'Welcome, guest'; }}If your codebase treats that shape as acceptable, silence just it:
rules: avoid_non_null_assertion: ignore_checked_fields: trueRelationship to the official Dart lints
Section titled “Relationship to the official Dart lints”The SDK linter has no equivalent rule — a request for one (sdk#59476) was closed without shipping. Two official rules cover strict subsets of what this rule reports, so enabling both means those cases are reported twice:
| Official rule | Overlap |
|---|---|
unnecessary_null_checks |
Flags only bangs whose surrounding context already accepts null — the redundant ones |
null_check_on_nullable_type_parameter |
Flags only a bang on a potentially nullable generic type parameter |
If the duplicate diagnostics bother you, disable the narrower SDK rules — this rule already reports everything they do.
Known limitations
Section titled “Known limitations”There is deliberately no quick fix. Rewriting ! to ?. changes behaviour: an expression that used to throw would instead silently evaluate to null, which moves the failure somewhere else rather than fixing it. Choosing between ?., ??, a guard, or restructuring the types is a judgement call the author has to make.
For the common case of a ! on a field inside an if (field != null) guard, two assists do the rewrite for you — Convert null check to pattern (semantics-preserving) and Convert null check to destructuring pattern (narrows the condition). They are assists rather than fixes because both restructure the whole if, above the node this rule reports on.
A ! inside a pattern (case var x!) is a NullAssertPattern, not the postfix operator, and is not reported.
Options
Section titled “Options”many_lints: rules: avoid_non_null_assertion: ignore_checked_fields: true ignore_map_indexes: falserules: avoid_non_null_assertion: ignore_checked_fields: true ignore_map_indexes: false| Option | Type | Default | Description |
|---|---|---|---|
ignore_checked_fields |
bool | false |
Don’t report a ! on a field an enclosing if (field != null) already checked. Off by default because type promotion does not apply to fields — the bang is genuinely what makes the code compile, and the field can still change between the check and the use |
ignore_map_indexes |
bool | true |
Allow the idiomatic map[key]! spelling. The pedantic preset changes this default to false |
Turning this rule off
Section titled “Turning this rule off”This rule is in the opinionated preset, so it is on with
preset: opinionated, or by name:
rules: avoid_non_null_assertion: trueTo turn it off again:
rules: avoid_non_null_assertion: falseTo keep the rule on but skip certain paths, use per-rule exclude.
Related rules
Section titled “Related rules”function_always_returns_null— A nullable-returning function whose every path returns null.avoid_accessing_other_classes_private_members— Make the underscore mean what everyone reads it as.avoid_commented_out_code— Detect and flag commented-out code.avoid_complex_conditions— Keep boolean conditions within an operand budget.