Null Safety in Kotlin: The End of the 'One Billion Dollar Mistake'
Table of Contents
💥 Theory: The Billion Dollar Mistake
Tony Hoare, the inventor of the null reference in 1965, called it “my billion-dollar mistake”. In Java (and C++, C#), any object can be null, leading to the dreaded NullPointerException (NPE) at runtime.
Kotlin solves this by moving the problem from Runtime to Compile time.
The Dual Type System
In Kotlin, the type hierarchy is duplicated.
String: can NEVER be null.String?: CAN be null.
String is a subtype of Any.
String? is a subtype of Any?.
The cool thing is that String is a subtype of String?. You can pass a String where String? is expected, but not vice versa.
🛠️ Null Safety Tools
1. Safe Call (?.)
The most used tool. If it is null, it does nothing and returns null.
val length: Int? = maybeNullString?.length
Note that the result is Int? (nullable), because if maybeNullString is null, length will be null.
2. Elvis Operator (?:)
Provides a default value if something is null.
val length: Int = maybeNullString?.length ?: 0
Now the result is Int (non-nullable). It’s like an if-else in one line.
Advanced Trick: throw and return are expressions in Kotlin, so you can use them with Elvis.
val user = repository.findUser(id) ?: throw UserNotFoundException(id)
val parent = view.parent ?: return
3. Not-Null Assertion (!!)
“I know what I’m doing, trust me”. Throws NPE if null.
Golden Rule: Avoid it. If you see !! in a Code Review, ask for a very good justification. Almost always there is a better way (?: or requireNotNull).
4. Safe Cast (as?)
Tries to cast, returns null if it fails.
val fragment = view as? UserFragment
// fragment is UserFragment?
🧠 Advanced Techniques
1. let for safe blocks
If you have a mutable nullable variable, Kotlin won’t let you smart cast because it might have been changed by another thread.
var user: User? = ...
if (user != null) {
// user.name // Error: Smart cast impossible
}
// Solution: Immutable capture with let
user?.let { safeUser ->
print(safeUser.name)
}
2. Extension Functions on Nullable Types
You can define functions that accept null as a receiver.
fun String?.isNullOrEmpty(): Boolean {
return this == null || this.isEmpty()
}
// Usage (don't need ?. safe call)
val s: String? = null
if (s.isNullOrEmpty()) { ... }
3. Java Interoperability (Platform Types)
When calling Java from Kotlin, types are String! (Platform Type). Kotlin doesn’t know if it’s nullable or not.
- You can treat it as
String(risk of NPE). - You can treat it as
String?(safe).
Best Practice: Always annotate your Java code with @Nullable and @NonNull so Kotlin knows what to do.
🎯 Conclusion
Kotlin’s Null Safety is not just syntactic sugar; it’s a fundamental design feature that forces you to think about “nullability” as part of your API contract. By adopting these patterns, you eliminate an entire category of bugs from your application.
You might also be interested in
Mastering Kotlin Scope Functions: let, run, with, apply, also
let, run, with, apply, also... Which one to use and why? A deep dive into Kotlin Scope Functions, language design theory, and best practices.
The Billion Dollar Mistake: Null Safety in Kotlin vs Java
Why is 'null' so dangerous? Discover how Kotlin solves the NullPointerException problem at the compiler level and how to design null-safe APIs.
Clean Architecture: The Ultimate Guide for Modern Android
Demystifying Clean Architecture: A deep dive into layers, dependencies, and data flow to build indestructible Android apps.