Dart Null Safety: Prevent Null Dereferences With Explicit Nullable Types
Dart makes non‑nullable the default and uses ? to opt into nullability. A worked function signature shows how the analyzer forces null checks and promotions, and where migration and interop weaken guarantees.
23 Sept 2025, 17:50 UTC

Null dereference crashes are a compile-time problem in sound null safety
In Dart with sound null safety, the default is non‑nullable. A type like String means the value can never be null at runtime. Only String? explicitly allows null. The analyzer rejects code that could dereference null, so the crash is caught before shipping.
The practical rule is: make the type reflect the reality. If a value can be absent, type it as nullable and handle the absence. If it must be present, keep it non‑nullable and force callers to provide it.
Worked example: a function signature that forces safe control flow
Consider a display helper where the name may be missing.
String describe(String? name) {
if (name == null) {
return 'Anonymous';
}
// Inside this branch name is promoted to String
return 'Hello, ${name.length} chars';
}
With the signature String describe(String? name), the parameter type is nullable. The analyzer forbids name.length at the top level because the static type admits null. The explicit null check promotes name to non‑nullable inside the if block, so the call is allowed.
Alternatives that stay safe:
- Null‑aware access:
name?.lengthreturnsint?and does not throw. - Defaulting:
final display = name ?? 'Anonymous';
To require a value, remove the ? and use required:
String greet({required String name}) {
return 'Hi $name';
}
Callers must pass name, and the analyzer will flag a missing argument at the call site.
Why the analyzer blocks unsafe dereference
Sound null safety tracks nullability through flow analysis. For int? x = null, the expression x + 1 is rejected because + is not defined for a nullable int. The error is a compile‑time type error, not a runtime exception.
The null assertion operator ! opts out of the check:
int? maybe;
int value = maybe!; // compiles, throws LateError if maybe is null at runtime
Use ! only when you have a separate invariant that guarantees non‑null. It converts a static error into a potential runtime crash.
late defers the check:
late String title;
void init() { title = 'Loaded'; }
Reading title before init throws LateInitializationError. late is useful for fields initialized in initState or constructors where the initializer depends on this, but it does not make a value non‑nullable; it only postpones the check.
Migration and interop limits
Sound null safety requires a minimum SDK constraint. In pubspec.yaml set:
environment:
sdk: '>=2.12.0 <4.0.0'
Run dart analyze in the project root. The analyzer will list type errors for nullable misuse. No elevated permissions are required.
To see blocking dependencies, run:
dart pub outdated --mode=null-safety
in the project root. It lists packages that are not yet null‑safe. Until every imported library is migrated, Dart runs in mixed mode and values from non‑null‑safe libraries are treated as nullable, weakening guarantees at the boundary.
Interop with JavaScript, platform channels, or dynamic data is another seam. Assume external values may be null even if the Dart type says otherwise, and validate at the boundary.
Common mistakes to avoid
- Overusing
!to silence the analyzer. Prefer a null check,??default, or?.guard. - Using
latefor convenience on fields that may never be initialized. This hides initialization bugs behind runtime errors. - Assuming
?.guards a whole chain.name?.lengthis safe, butname?.length!is not; the!reintroduces risk after the guard. - Forgetting the library boundary. A return type from a non‑null‑safe package is effectively
T?in sound code.
Check the result by running dart analyze on a file with a nullable misuse, such as int? x = null; print(x + 1);. The analyzer should report an error about the operator not being defined for a nullable type. Fixing the type or adding a null check clears the error.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.