Migrating a Dart Project to Sound Null Safety: A Step‑by‑Step Walkthrough
Learn how to enable Dart’s sound null safety, run the migration tool, handle dependency updates, and verify that null‑reference bugs are caught at compile time.
06 Apr 2026, 14:37 UTC

Why null‑reference errors still appear in Dart apps
Even experienced Dart developers occasionally see a NullThrownError in production when a variable that was assumed to hold a value actually contains null. These bugs are hard to trace because they surface only at runtime, often after a specific user action. Dart’s sound null safety was introduced to eliminate this class of errors by making the type system aware of nullability.
Thesis: enabling sound null safety catches null‑access bugs at compile time
When sound null safety is turned on, the Dart analyzer treats every variable as non‑nullable unless you explicitly mark it with ?. Any attempt to assign null to a non‑nullable variable, or to dereference a possibly‑null value without a check, is reported as an error before the code is run. This gives you immediate feedback while editing and reduces the chance of shipping a null‑reference crash.
Preparing the project
Check the SDK version. Sound null safety is available from SDK 2.12 and becomes the default in 2.14+. Run this in your project root:
dart --versionIf the version is lower than 2.12, upgrade the Dart SDK first.
Ensure your dependencies support null safety. Open
pubspec.yamland look for versions marked as null‑safe (usually >= the version that introduced the feature). If a dependency is still unsound, you may need to fork it, patch it, or wait for an update.Commit or stash your current work. The migration tool rewrites files; having a clean commit lets you inspect the diff easily.
Running the migration tool
The dart migrate command analyzes your codebase, proposes the minimal changes needed to make every variable explicitly nullable or non‑nullable, and can apply them automatically.
First, see what the tool would change without touching any files:
dart migrate --dry-runThis prints a list of suggested edits. Review them to confirm they match your intent.
If the dry‑run looks good, apply the migration:
dart migrateThe tool will create a backup of each modified file (e.g.,
file.dart.migration-backup) and overwrite the originals with the migrated version.After migration, run the analyzer to verify there are no new errors:
dart analyzeIf you see errors, they usually point to places where the tool could not infer nullability (e.g., complex generics or legacy patterns). You’ll need to adjust those manually.
Worked example: before and after migration
Consider a simple library that fetches user data:
// user_repository.dart (pre‑migration)
class UserRepository {
String? _cachedName;
String getName() {
// Potential null‑access if _cachedName is null
return _cachedName.toUpperCase();
}
}
Running dart migrate --dry-run suggests making _cachedName explicitly nullable and adding a null check:
// user_repository.dart (post‑migration)
class UserRepository {
String? _cachedName;
String getName() {
return _cachedName?.toUpperCase() ?? '';
}
}
The analyzer now knows that _cachedName can be null, and the ?. operator prevents a runtime error. If you mistakenly wrote _cachedName.toUpperCase() after migration, the analyzer would flag an error.
Trade‑offs and limitations
Dependency updates may be required. Some older packages on pub.dev have not been migrated; using them can force the whole project into unsound mode unless you enable strict mode with
dart analyze --strict-raw-types.The migration tool cannot always infer the correct nullability for complex generic types or code that relies on reflection. Manual refactoring may still be tedious, especially in large codebases.
Enabling sound null safety changes the semantics of certain expressions (e.g.,
x ??= ybehaves differently whenxis nullable). Existing tests should be run to ensure behavior stays as expected.
Actionable closing steps
Run
dart analyze --strict-raw-typeson your migrated code to confirm that no unsound dependencies are silently weakening safety.Add a null‑safety test to your CI pipeline: create a file that attempts to assign
nullto a non‑nullable variable and verify the analyzer fails the build.Keep your Dart SDK up to date (currently 2.19+ as of this writing) to benefit from ongoing null‑safety improvements and performance optimizations.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.