Mastering Dart Sound Null Safety: Migration, Usage, and Common Pitfalls
Enable Dart’s sound null safety, migrate code, and avoid common pitfalls. This guide walks through SDK setup, migration steps, nullable and late types, and practical checks to keep your app free of null‑reference crashes.
29 Jul 2026, 23:10 UTC

Why Null Safety Matters
Null‑reference bugs are the most frequent source of crashes in Dart applications. Sound null safety guarantees that any variable not marked with a question mark (?) can never contain null. The compiler then detects potential dereferences before the program runs, turning runtime errors into compile‑time errors.
Enabling the Feature
Sound null safety is available in Dart 2.12 and newer. To activate it in an existing project:
- Open
pubspec.yamland set the SDK constraint to a version that supports null safety. For example:environment: sdk: ">=2.12.0 <3.0.0" - Run
dart pub getto fetch dependencies. All dependencies must also be null‑safe; otherwise the migration will fail. - Execute the migration tool:
The tool analyzes the codebase, suggests nullable anddart migratelateannotations, and writes a migration plan to a new filemigration.dart. - Review the plan in a browser (the tool opens a local web page). Accept changes and apply them with:
This rewrites the source files in place.dart migrate --apply-changes
Understanding Nullability Annotations
After migration, three primary annotations appear in your code:
- Non‑nullable types – e.g.,
String name = 'Alice';. These must be initialized and can never holdnull. - Nullable types – e.g.,
int? count;. The variable can holdnull, and you must check for null before use. - Late variables – e.g.,
late DateTime timestamp;. The compiler trusts you to assign a non‑null value before first read.
Example:
void main() {
String greeting = 'Hello'; // non‑nullable, initialized
int? attempts; // nullable, defaults to null
late DateTime start; // late, must be set before use
print(greeting);
print(attempts ?? 0); // null‑aware operator
start = DateTime.now();
print(start);
}
Attempting greeting = null; would produce a compile‑time error: error: A value of type 'Null' can't be assigned to a variable of type 'String'.
Flow Analysis and Exhaustiveness
The analyzer tracks variable assignments along all code paths. For example, a switch on a non‑nullable enum must cover every case; otherwise the compiler flags it as non‑exhaustive. This guarantees that a null dereference cannot slip through a missing branch.
Interoperability Limits
Null safety is sound only when all code is null‑safe. Mixing with dynamic or JavaScript (via dart:js) bypasses the analyzer’s guarantees. In such cases you must explicitly check for null after a dynamic cast:
dynamic data = fetchFromJs();
if (data is String) {
print(data);
} else {
// Handle unexpected null or type
}
Common Mistakes and How to Avoid Them
- Overusing the null assertion operator (
!): This forces a value to be non‑null at runtime, potentially re‑introducing the very bug null safety eliminates. Use it sparingly and only when you can guarantee the value is set. - Neglecting third‑party packages: If a dependency hasn't been migrated, the entire project may fail to compile. Check
pubspec.lockfor null‑safe versions or usedart pub outdated --mode=null-safetyto identify gaps. - Assuming
lateis safe: Alatefield that is never assigned will throw aLateInitializationErrorat runtime. Guard usage with checks or initialize early. - Mixing sound and unsound libraries: Importing a non‑null‑safe library will downgrade the entire compilation unit to unsound mode. Keep all imports null‑safe or use
dart:core’s null‑aware features carefully.
Practical Verification Checklist
- Run
dart analyzeafter migration. All errors should be resolved or intentionally suppressed with comments. - Execute unit tests with
dart test. No tests should fail due to null dereferences. - For critical paths, add runtime checks:
assert(variable != null, 'Variable must be initialized'); - Monitor production logs for
LateInitializationErrororNullThrownErrorafter deployment.
Conclusion
Sound null safety transforms Dart from a language that tolerates null dereferences into one that guarantees safety at compile time. By setting the SDK constraint, running the migration tool, and understanding the three nullability annotations, developers can catch bugs early, reduce runtime crashes, and write clearer code. Just remember to keep all dependencies null‑safe, use late responsibly, and avoid the temptation to suppress errors with ! or dynamic without checks.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.