Dart Null Safety: Enable, Migrate, and Avoid Common Pitfalls
Dart's sound null safety makes types non‑nullable by default, catching null‑dereference bugs at compile time. Shows enabling in pubspec.yaml, non‑nullable and late variables, dart migrate usage, and pitfalls like unsafe ! and late init errors.
14 Mar 2026, 22:48 UTC

The Problem and the Takeaway
Null dereference errors are a leading cause of runtime crashes in Dart applications. Dart's sound null safety eliminates this class of bugs by making all types non‑nullable by default. The compiler now flags any variable that could be null unless you explicitly opt in with a ? suffix. The practical result: you catch missing null checks at compile time instead of discovering them in production.
Enabling Null Safety in a Project
Null safety is tied to the SDK version declared in pubspec.yaml. Set the lower bound to at least 2.12.0 and run dart pub get to activate the analyzer's strict mode.
# pubspec.yaml
environment:
sdk: ">=2.12.0 <3.0.0"
Run the following in your project root (requires write permission to the pubspec.lock file):
cd
dart pub get
After this, dart analyze will treat every declaration as non‑nullable unless marked with ?.
Core Mechanics: Non‑Nullable Types and late Variables
The example below shows a non‑nullable int, a late string that is initialized before first use, and a deliberately nullable String? parameter.
// lib/main.dart
void main() {
int counter = 0; // non‑nullable, must be initialized
late String userName; // non‑nullable, initialization deferred
userName = fetchUserName(); // initialization happens here
print('User: $userName, count: $counter');
}
String fetchUserName() => 'Alice';
// A function that accepts a nullable argument
void greet(String? name) {
if (name != null) {
print('Hello, $name');
} else {
print('Hello, guest');
}
}
Key points:
countercannot be assignednull; the analyzer will error if you try.late userNamepromises initialization before first read. Accessing it early throws aLateInitializationErrorat runtime.String? nameexplicitly allowsnull; theif (name != null)check satisfies the flow analysis.
Migrating Legacy Code with dart migrate
For existing codebases, the migration tool automates the bulk of the work. Run it from the project root after enabling the SDK constraint:
cd
dart migrate
The tool prints a diff showing where it proposes adding ? or !. Review the output carefully—accepting the suggestions without scrutiny can introduce unsafe ! assertions. When satisfied, apply the changes:
dart migrate --apply
The migration also produces a migration_report.json summarizing the modifications; keep it for audit purposes.
Common Mistakes and Limits
1. Unsafe Use of the Null‑Assertion Operator !
! tells the analyzer "trust me, this isn't null." If the value is actually null, a TypeError occurs at runtime. Only use ! after a proven null check or when the variable is guaranteed initialized (e.g., after a late assignment).
2. Mixing Legacy Packages
Dependencies that haven't migrated to null safety cause dependency_overridden_by_dependency warnings. You can temporarily silence them with a dependency_overrides block in pubspec.yaml, but the real fix is waiting for the package maintainer to publish a null‑safe version.
3. Forgetting ? During Refactor
Changing a return type from String to String? without updating call sites leads to cascade errors. The analyzer will flag every place that now receives a nullable value.
4. late Initialization Order
A late field that is read before its initializer runs throws LateInitializationError. This often happens in complex initialization graphs or when a field depends on another late field. Verify initialization order with tests or by adding explicit assert checks in debug builds.
Verification Checklist
- Static analysis:
dart analyzeshould report zero null‑safety errors. - Migration review: Inspect
migration_report.jsonfor unexpected!insertions. - Runtime smoke test:
dart runmust start withoutLateInitializationErrororTypeError. - Unit tests: Execute the test suite (
dart test) to ensure no regressions from the migration.
Limitations
Null safety is a compile‑time guarantee; it does not change runtime representation of objects. Interoperability with JavaScript (via dart:js_interop) or native code (via FFI) still requires manual null checks because external boundaries are not soundly typed. Additionally, late variables shift some null‑related failures from compile time to runtime, so they should be used sparingly and only when initialization order is provably correct.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.