Kotlin null safety
Distinguish nullable from non-null types, and use ?., ?:, let, and !! deliberately.
fun describe(name: String?): String {
val length = name?.length ?: -1
return name?.let { "name '$it' has $length characters" } ?: "no name"
}
fun main() {
println(describe("Ada"))
println(describe(null))
val items: List<String?> = listOf("a", null, "c")
println(items.filterNotNull().joinToString("+"))
val text: String? = "kotlin"
println(text!!.uppercase())
val missing: String? = null
val result = runCatching { missing!!.length }
println(result.exceptionOrNull() is NullPointerException)
}name 'Ada' has 3 characters no name a+c KOTLIN true
Explanation
In Kotlin a type is non-null by default: a String can never be null, and the compiler enforces it. A nullable type is written with a question mark, String?, and the compiler refuses to call methods on it until you have dealt with the null case. This moves a whole class of NullPointerException errors from run time to compile time.
The safe-call operator ?. calls a member only if the receiver is not null and otherwise gives null. The Elvis operator ?: supplies a default for a null result. let runs a block with the value only if it is not null. filterNotNull removes nulls from a collection. The not-null assertion !! tells the compiler to trust you and throws a NullPointerException if you are wrong, so use it rarely.
Explore the dry run
fun describe(name: String?): String { val length = name?.length ?: -1 return name?.let { "name '$it' has $length characters" } ?: "no name"} fun main() { println(describe("Ada")) println(describe(null)) val items: List<String?> = listOf("a", null, "c") println(items.filterNotNull().joinToString("+")) val text: String? = "kotlin" println(text!!.uppercase()) val missing: String? = null val result = runCatching { missing!!.length } println(result.exceptionOrNull() is NullPointerException)}Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| ?. gives null for a null name, and ?: replaces that null with -1. | — | — | |
| let runs only when name is not null. Otherwise the Elvis operator returns the fallback text. | — | — | |
| A non-null name goes through let. | — | name 'Ada' has 3 characters | |
| A null name takes the fallback. | — | name 'Ada' has 3 characters no name | |
| filterNotNull drops the null and changes the type to List<String>. | — | name 'Ada' has 3 characters no name a+c | |
| !! asserts that text is not null. Here it is, so the call works. | — | name 'Ada' has 3 characters no name a+c KOTLIN | |
| !! on a null value throws, and runCatching captures the exception. | — | name 'Ada' has 3 characters no name a+c KOTLIN | |
| The assertion failed with a NullPointerException. | — | name 'Ada' has 3 characters no name a+c KOTLIN true |
Cost and common mistakes
Each operation is O(1); filterNotNull is O(n).
- Platform types from Java code can be null even though Kotlin does not mark them. Treat values from Java APIs with care.
- !! turns the compiler's protection off. Prefer ?., ?:, or an early return when a value is missing.
- lateinit and the null check inside a lambda interact with smart casts. The compiler smart-casts a val after a null check, but not a var that another thread could change.
Try it yourself
What does null?.length return?
Reveal answer and reasoning
null. The safe call skips the property access when the receiver is null, and the result type is Int?.