Adopting Dart Sound Null Safety: A Practical Migration Walkthrough
Learn how Dart’s sound null safety moves null‑related bugs from runtime to compile time, with a concrete JSON‑parsing example, migration steps, and practical trade‑offs.
16 Jul 2025, 06:23 UTC

The Problem: Null‑Related Crashes in Dart
In pre‑null‑safety Dart, a variable could hold null unless you explicitly checked it. Forgetting a check led to runtime errors that felt like NullPointerException in other languages, often surfacing only after deployment and hurting user experience.
Thesis: Sound Null Safety Shifts Errors to Compile Time
Dart’s sound null safety makes every type non‑nullable by default. You must annotate a type with ? when it can legitimately be null. The analyzer then flags any possible dereference of a potentially null value before the code runs, turning many runtime crashes into static errors.
Worked Example: Making a JSON Parser Null‑Safe
Consider a simple function that extracts a user’s email from a JSON map:
String getEmail(Map json) {
return json['user']['email'] as String;
}
This version assumes the nested keys exist and are strings. If any part is missing or null, the cast throws at runtime.
First, annotate the parameters and return type to reflect what can be null:
String? getEmail(Map? json) {
if (json == null) return null;
final user = json['user'] as Map?;
if (user == null) return null;
return user['email'] as String?;
}
Now the analyzer knows json, user, and the final result may be null. Any caller that forgets to check the return value gets a compile‑time warning.
If you are certain the email will always be present in a trusted data source, you can use late or a default instead of propagating nulls:
String getEmail(Map json) {
final user = json['user'] as Map;
return user['email'] as String ?? '[contact removed]';
}
This version eliminates nullable returns while still guarding against missing keys with a fallback.
Trade‑offs and Gotchas
- Verbosity: Adding
?and null checks can make code longer, especially during migration. - Misuse of
lateand!: Usinglatewithout proper initialization or applying the null‑assertion operator (!) re‑introduces runtime null errors, weakening the guarantee. - Dependency friction: Packages that have not adopted null safety may require version pins or temporary workarounds.
Actionable Steps to Migrate and Verify
- Create a fresh null‑safe project (run in your shell, no special permissions needed):
This generates adart create --null-safety my_app cd my_apppubspec.yamlwithenvironment: sdk: '>=2.12.0 <3.0.0''. - Run the analyzer to see the baseline:
Expect zero null‑safety warnings if the template is sound.dart analyze - Migrate existing code using the migration tool:
The tool edits files in place; review the diff before committing.dart migrate --apply-changes - Introduce a deliberate error to confirm the analyzer works: remove a required
?from a variable, rundart analyze, and observe the new warning. Fix it and verify the warning disappears. - Exercise the app to ensure no null‑related exceptions appear at runtime:
Test code paths that previously threw null errors; they should now be handled by the null‑safe logic or produce a compile‑time error instead.dart run - Check dependencies: run
dart pub outdated --mode=null-safetyto see which packages need updating.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.