Kotlin’s type system distinguishes between types that can hold null and types that can’t, and enforces the difference at compile time. This is the language feature most directly aimed at eliminating NullPointerException, historically one of the most common crashes in Java-based Android apps.
Nullable vs Non-Nullable Types
A plain type like String can never hold null — the compiler rejects it. Adding ? makes the type nullable:
var name: String = "Alex"
name = null // compile error: null can not be a value of a non-null type String
var nickname: String? = "Al"
nickname = null // fine, nickname is explicitly nullable
Because this is enforced at compile time, a function that takes a plain String parameter can rely on it never being null, with no defensive null check required.
The Safe Call Operator: ?.
Calling a method or property directly on a nullable type is a compile error — you have to handle the null case. The safe call operator ?. does that concisely: it calls the member if the value isn’t null, and evaluates to null instead of throwing if it is.
val nickname: String? = null
val length = nickname?.length // length is Int?, and is null here, no crash
The Elvis Operator: ?:
?: provides a fallback value when the left side is null:
val displayName = nickname ?: "Anonymous"
Combined with a safe call, this is a common one-liner for “use this value, or a default if it’s null”:
val length = nickname?.length ?: 0
The Non-Null Assertion: !!
!! tells the compiler “trust me, this isn’t null” — and if you’re wrong, it throws a NullPointerException at that exact line, which is precisely the exception null safety exists to prevent:
val length = nickname!!.length // crashes here if nickname is actually null
Treat !! as a last resort, not a routine tool for silencing the compiler. Reaching for it usually means either the type shouldn’t be nullable in the first place, or the null case genuinely needs to be handled rather than asserted away.
Smart Casts and let
Inside an explicit null check, Kotlin’s compiler automatically treats the value as non-nullable for the rest of that scope — no cast needed:
if (nickname != null) {
println(nickname.length) // smart-cast to String, no ?. needed here
}
The let function is a common alternative for the same purpose, especially with a nullable local variable or expression:
nickname?.let {
println(it.length) // only runs if nickname was not null
}
A Practical Rule
Make a type nullable only when null is a genuinely meaningful value for it — “no user is logged in”, “this field is optional.” For everything else, keep it non-nullable and let the compiler guarantee it’s never null, which is the entire point of the feature.