Null Safety ⭐
The single biggest upgrade from Java. Kotlin's type system makes null explicit — eliminating NullPointerExceptions at compile time.
Tony Hoare, inventor of the null reference, called it his "billion-dollar mistake" — because null dereferences have caused countless production crashes. Kotlin was designed from day one to make null safety a first-class language feature.
Non-Nullable vs Nullable Types
In Kotlin, every type is non-nullable by default. You must explicitly opt in to allow null with a ? suffix:
String name = "Alice";
name = null; // perfectly legal!
// No compile-time protection:
System.out.println(name.length());
// ☠️ NullPointerException at runtime!
var name: String = "Alice"
// name = null // ❌ Won't compile!
// Nullable type uses '?'
var name2: String? = "Alice"
name2 = null // ✅ allowed
// Kotlin prevents you from calling .length
// on a nullable String? without checking
// name2.length // ❌ compile error
The Safe Call Operator ?.
Call methods on nullable values without manual null checks. Returns null if the receiver is null:
String name = getName(); // might be null
int length;
if (name != null) {
length = name.length();
} else {
length = 0;
}
// Verbose null-check chain:
if (user != null
&& user.getProfile() != null) {
System.out.println(
user.getProfile().getAvatarUrl());
}
val name: String? = getName()
val length = name?.length ?: 0
// Returns null if name is null
// Chaining safe calls — clean!
val avatarUrl = user?.profile?.avatarUrl
println(avatarUrl) // null if any is null
// Only execute if non-null:
name?.let { n ->
println("Name length: ${n.length}")
}
The Elvis Operator ?:
Provide a default value when a nullable expression is null. This replaces Java's ternary-with-null-check pattern:
String name = user.getName();
String displayName = name != null
? name
: "Anonymous";
int age = user.getAge() != null
? user.getAge()
: 0;
val name: String? = user.name
val displayName = name ?: "Anonymous"
val age = user.age ?: 0
// Elvis can also throw:
val userId = user.id
?: throw IllegalStateException("No ID!")
Non-Null Assertion !!
Tells the compiler "I guarantee this is not null." Throws NullPointerException if it is. Use sparingly — it's an escape hatch, not a solution:
val name: String? = "Alice"
// !! asserts non-null — throws NPE if null
val length = name!!.length // 5
// ❌ Anti-pattern — defeats null safety
val bad: String? = null
val crash = bad!!.length // ☠️ NPE!
!! whenever possibleThink of !! as a code smell. Every !! is a potential NPE. Prefer safe calls (?.) and Elvis (?:) instead. Reserve !! for cases where null is truly a program bug, not a normal state.
let — Execute Block on Non-Null
val email: String? = getUserEmail()
// Execute only if email is not null
email?.let { e ->
sendWelcomeEmail(e)
println("Email sent to $e")
}
// 'e' is guaranteed non-null inside the block
// Also useful for transformations:
val nameLen: Int? = name?.let { it.length }
Null Safety Quick Reference
| Operator / Pattern | Meaning | When to use |
|---|---|---|
String? | Nullable type | When the value can legitimately be null |
x?.method() | Safe call — returns null if x is null | Chaining nullable calls |
x ?: default | Elvis — fallback if null | Providing default values |
x!! | Non-null assert — throws NPE if null | Only when null is a bug (sparingly) |
x?.let { } | Execute block if non-null | Side effects on nullable values |
if (x != null) | Explicit check — smart cast inside block | Complex logic needing non-null reference |