Skip to content

avoid_non_null_assertion

v1.0.0WarningConfigurableCode Quality

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}';
}
}

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;
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’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:

many_lints.yaml
rules:
avoid_non_null_assertion:
ignore_map_indexes: false

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:

many_lints.yaml
rules:
avoid_non_null_assertion:
ignore_checked_fields: true

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.

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.

analysis_options.yaml
many_lints:
rules:
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

This rule is in the opinionated preset, so it is on with preset: opinionated, or by name:

many_lints.yaml
rules:
avoid_non_null_assertion: true

To turn it off again:

many_lints.yaml
rules:
avoid_non_null_assertion: false

To keep the rule on but skip certain paths, use per-rule exclude.