Question
Kotlin Nullable Types: Idiomatic Ways to Handle, Convert, and Assert Non-Null Values
Question
In Kotlin, how should you idiomatically work with a nullable type such as Xyz? when you need to reference it or treat it as a non-nullable Xyz?
For example, this code produces an error:
val something: Xyz? = createPossiblyNullXyz()
something.foo() // Error: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Xyz?
However, if a null check is added first, the call is allowed:
val something: Xyz? = createPossiblyNullXyz()
if (something != null) {
something.foo()
}
Why does this work?
Also, how can a nullable value be treated as non-null without always writing an if check, when you know for certain it is not actually null?
For example, when retrieving a value from a map:
val map = mapOf("a" to 65, "b" to 66, "c" to 67)
val something = map.get("a")
something.toLong() // Error: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Int?
Here, get() returns Int? because the key might not exist. What is the idiomatic way to turn that result into a non-null value when you know the key is present?
Short Answer
By the end of this page, you will understand how Kotlin nullable types work, why a prior null check allows direct access, and the idiomatic tools for handling nullable values: safe calls, if checks, let, Elvis ?:, requireNotNull, checkNotNull, and the non-null assertion operator !!. You will also learn the best way to access map values when a missing key is or is not acceptable.
Concept
Kotlin distinguishes between nullable and non-nullable types.
Xyzmeans the value cannot benullXyz?means the value may benull
This is one of Kotlin's key safety features. It helps prevent the classic NullPointerException problem by making nullability part of the type system.
If a variable has type Xyz?, Kotlin will not let you call methods on it directly, because the value might be null at runtime.
val something: Xyz? = createPossiblyNullXyz()
something.foo() // Not allowed
Kotlin requires you to do one of the following:
- prove the value is not null
- safely handle the null case
- explicitly assert that it is not null
That is why this works:
if (something != null) {
something.foo()
}
Inside the if block, Kotlin uses smart casting. Because you checked something != null, the compiler can treat as instead of in that scope.
Mental Model
Think of a nullable value like a box that may or may not contain an item.
Xyz= a box that definitely contains anXyzXyz?= a box that might be empty
Kotlin does not let you use the item unless you first deal with the possibility that the box is empty.
There are several ways to handle it:
if (x != null)= open the box and verify something is insidex?.foo()= use the item only if the box is not emptyx ?: fallback= if the box is empty, use a backup itemx!!= say, "I guarantee this box is not empty"
That last option is like skipping the safety check. If you are wrong, the program crashes.
For map lookups, imagine asking a dictionary for a word. The dictionary may or may not contain it, so Kotlin gives you a nullable result unless you use an API that says, "This key must exist."
Syntax and Examples
Core syntax
val a: String? = null // nullable
val b: String = "hello" // non-nullable
1. Safe call ?.
val name: String? = getName()
val length = name?.length
If name is null, length becomes null.
2. Elvis operator ?:
val name: String? = getName()
val length = name?.length ?: 0
If name is null, length becomes 0.
3. Smart cast after null check
Step by Step Execution
Consider this example:
val map = mapOf("a" to 65, "b" to 66)
val number = map["a"] ?: error("Key not found")
val result = number.toLong()
println(result)
Step by step
-
mapis created with two entries:"a" -> 65"b" -> 66
-
map["a"]performs a lookup.- The return type is
Int? - Even though
"a"exists here, the map API still returns a nullable type because some keys might be missing
- The return type is
-
?: error("Key not found")handles the null case.- If the result is not null, that value is used
- If the result is null, the program throws an exception immediately
-
After the Elvis operator,
numberhas typeInt, not
Real World Use Cases
API and JSON parsing
Many API fields are optional.
val email = user.email ?: "no-email@example.com"
Form input validation
User input may be missing or blank.
val username = requireNotNull(inputUsername) { "Username is required" }
Database access
A query may return no result.
val customer = repository.findById(id)
customer?.let {
sendEmail(it)
}
Configuration values
Use a fallback if an environment setting is absent.
val port = System.getenv("APP_PORT")?.toInt() ?: 8080
Map and cache lookups
Caches and maps often return nullable results.
val user = cache[userId] ?: loadUser(userId)
Defensive programming
Stop early when a required value is unexpectedly null.
token = checkNotNull(session.token) { }
Real Codebase Usage
In real Kotlin codebases, developers usually choose a nullable-handling style based on intent.
Common patterns
Guard clauses
Fail early when a required value is missing.
val user = requireNotNull(currentUser) { "User must be logged in" }
This avoids deeply nested code.
Early return
Return from a function if a nullable value is absent.
fun printLength(text: String?) {
val value = text ?: return
println(value.length)
}
Fallback values
Use sensible defaults.
val timeout = config.timeout ?: 30
Transforming nullable values
Use safe calls and scope functions.
val upper = name?.trim()?.uppercase()
Required map entries
Use getValue() when the key must exist.
Common Mistakes
1. Using !! too often
Broken style:
val name: String? = getName()
println(name!!.length)
This works only if name is truly not null. If you are wrong, it crashes.
Better:
val name = requireNotNull(getName()) { "Name is required" }
println(name.length)
2. Forgetting that map lookup returns nullable
Broken code:
val map = mapOf("a" to 1)
val value = map["a"]
println(value.toString())
value is Int?, not Int.
Better:
val value = map.getValue("a")
println(value.toString())
or
val value = map[] ?:
println(value.toString())
Comparisons
| Approach | Result Type | If value is null | Best used when |
|---|---|---|---|
x?.foo() | Nullable result | Returns null | Null is acceptable |
x ?: fallback | Non-null if fallback is non-null | Uses fallback | You want a default |
if (x != null) | Non-null inside block | Skips block | You need multiple operations |
x?.let { ... } | Depends on block | Skips block | You want scoped non-null work |
x!! | Non-null |
Cheat Sheet
Nullable basics
val a: String = "hello" // non-null
val b: String? = null // nullable
Access patterns
x?.foo() // safe call
x ?: fallback // Elvis operator
x!!.foo() // force non-null, may crash
Smart cast
if (x != null) {
x.foo()
}
Run code only if not null
x?.let {
println(it)
}
Fail fast with message
val value = requireNotNull(x) { "x must not be null" }
val state = checkNotNull(y) { "y was not initialized" }
Map lookups
val value1 = map[key] // V?
val value2 = map.getValue(key) // V
value3 = map[key] ?: default
FAQ
Why does Kotlin allow access after if (x != null)?
Kotlin uses smart casting. After that check, the compiler knows x cannot be null inside the block, so it treats it as non-null.
What is the most idiomatic replacement for !!?
Often requireNotNull, checkNotNull, or ?: with a meaningful fallback or error is clearer and safer.
Should I use map[key]!! or map.getValue(key)?
If the key is required to exist, map.getValue(key) is usually more expressive. It communicates intent better than !!.
What is the difference between requireNotNull and checkNotNull?
requireNotNull is for validating inputs or arguments. checkNotNull is for validating internal program state.
Does ?. convert a nullable value to non-null?
No. It safely accesses the value and produces another nullable result.
Mini Project
Description
Build a small Kotlin program that reads user roles from a map and prints a formatted access level. This project demonstrates how to work with nullable map lookups, provide defaults, and fail fast when a required key is missing.
Goal
Create a program that safely handles nullable values from map access using ?:, getValue(), and requireNotNull.
Requirements
- Create a
Map<String, Int>containing at least three role names and numeric access levels. - Read one optional role that may or may not exist in the map.
- Print a default access level when the role is missing.
- Read one required role and fail clearly if it does not exist.
- Convert a non-null access level to
Longand print it.
Keep learning
Related questions
Accessing Kotlin Extension Functions from Java
Learn how Kotlin extension functions are compiled and how to call them correctly from Java with clear examples and common pitfalls.
Allow HTTP and HTTPS in Android 9 Pie with Network Security Configuration
Learn how Android 9 Pie handles cleartext HTTP traffic and how to allow HTTP and HTTPS safely using network security config.
Android AlarmManager Example: Scheduling Tasks with AlarmManager
Learn how to use Android AlarmManager to schedule tasks, set alarms, and handle broadcasts with a simple beginner example.