Kotlin Module 3 – Null safety
Nullable types, safe calls and Elvis
Declare nullable types in Kotlin, read them with safe calls and the Elvis operator, return early for a missing value, and see what not-null assertions throw.
What you will learn
- Declare nullable types and predict which uses of them the compiler refuses
- Read nullable values with safe-call chains and let, and predict where a chain stops
- Use the Elvis operator to supply a fallback, return early or throw when a value is null
- Explain what !! throws and replace it with a fallback, an early return or requireNotNull
- Test a nullable Boolean in a condition with == true or ?: false
Before you start
What you’ll learn · Before you start
What you will learn
- Declare nullable types and predict which uses of them the compiler refuses
- Read nullable values with safe-call chains and let, and predict where a chain stops
- Use the Elvis operator to supply a fallback, return early or throw when a value is null
- Explain what !! throws and replace it with a fallback, an early return or requireNotNull
- Test a nullable Boolean in a condition with == true or ?: false
Before you start
On this page
A train booking can be missing information at three levels: the PNR may not exist, a waitlisted booking has no coach
yet, and a coach may not have a berth for this passenger. Code that reads booking.coach.berth.number as if all three
were always there is the classic way a Java program ends in a NullPointerException.
Kotlin writes the gap into the type. A String always holds a string, a String? may hold null, and the compiler
refuses code that treats the second like the first. You will declare both kinds of type, read nullable values with
safe calls and let, use the Elvis operator ?: for fallbacks and early returns, and see exactly what the not-null
assertion !! throws when you use it anyway.
String and String? are different types
A booking form asks every passenger for a name and lets them leave the nickname empty. Declare the name as a String
and the nickname as a String?: the question mark makes a nullable type: A type written with ? after it, such as String?, whose values are those of the type plus null. The compiler refuses a plain call on it until the code says what happens when the value is null., one whose
values are the strings plus null.
// A booking form: every passenger has a name, and a nickname only if they typed one.
fun main() {
val name: String = "Asha"
println("name: $name, ${name.length} letters")
// Two passengers: one typed a nickname, the other left the box empty.
for (nickname in listOf<String?>("Ash", null)) {
println("nickname: $nickname, is it null? ${nickname == null}")
}
// Every String is also a String?, so this assignment needs no check.
val shown: String? = name
println("shown: $shown")
} Output
name: Asha, 4 letters nickname: Ash, is it null? false nickname: null, is it null? true shown: Asha
Recorded with Kotlin 2.4.20 on OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: kotlinc Types.kt -include-runtime -d main.jar && java -jar main.jar
The loop gives nickname a string, then null, and both print. The last line shows the direction that always works:
every String is also a String?, so a value moves from the non-null type to the nullable one without a check. The
other direction is where the compiler stops you:
// Three lines the compiler refuses: each one could let a null in where a String is promised.
fun main() {
val nickname: String? = "Ash"
val pnr: String = null
val shown: String = nickname
println(nickname.length)
} Output (exit status 1)
Printed as an error (standard error)
TypeErrors.kt:4:23: error: null cannot be a value of a non-null type 'String'.
val pnr: String = null
^^^^
TypeErrors.kt:5:23: error: initializer type mismatch: expected 'String', actual 'String?'.
val shown: String = nickname
^
TypeErrors.kt:6:21: error: only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type 'String?'.
println(nickname.length)
^
Recorded with Kotlin 2.4.20 on OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: kotlinc TypeErrors.kt -include-runtime -d main.jar && java -jar main.jar
Line 4 tries to put null into a String. Line 5 assigns a String? to a String and is refused although
nickname holds “Ash” at that moment: the compiler goes by the declared type, not by the value the variable happens
to hold. Line 6 calls length on a String?, and the message names the two calls that are allowed there, the safe
call ?. and the not-null assertion !!., both covered below.
| Behaviour | String |
String? |
|---|---|---|
Can hold null |
no | yes |
x.length |
compiles | refused |
| Goes into the other type | always | only after a decision about null |
All of this happens while compiling. When the program runs, a String? that holds a string is the same string object
as any other; the Kotlin guide to nullability says the two kinds of type are treated the same at run time.
Checkpoint
Check: String and String?
A safe call gives null instead of throwing
A berth number sits three steps away from its PNR, and each step can be missing. bookings[pnr] looks the PNR up in
a map, and a map lookup gives null for a key it does not hold, so its type is Booking?. A waitlisted booking has
no coach, and a coach may have no berth for this passenger yet. The safe call: The ?. operator: a?.b reads b only when a is not null and gives null otherwise, so a chain of safe calls stops at its first null instead of throwing. ?. reads a
member only when the value on its left is not null, and gives null when it is:
// A booking can be missing at three levels: the PNR may be unknown, the booking may have no coach yet
// (waitlisted), and the coach may have no berth for this passenger yet.
class Berth(val number: Int, val kind: String)
class Coach(val code: String, val berth: Berth?)
class Booking(val passenger: String, val coach: Coach?)
val bookings: Map<String, Booking> = mapOf(
"8240117365" to Booking("Asha", Coach("B2", Berth(34, "lower"))),
"8240117366" to Booking("Ravi", Coach("S5", null)),
"8240117367" to Booking("Meena", null),
)
// bookings[pnr] is a Booking? (null for an unknown PNR), so every step after it is a safe call.
fun berthNumber(pnr: String): Int? = bookings[pnr]?.coach?.berth?.number
fun main() {
for (pnr in listOf("8240117365", "8240117366", "8240117367", "8240117399")) {
println("$pnr -> ${berthNumber(pnr)}")
}
} Output
8240117365 -> 34 8240117366 -> null 8240117367 -> null 8240117399 -> null
Recorded with Kotlin 2.4.20 on OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: kotlinc Berth.kt -include-runtime -d main.jar && java -jar main.jar
Only Asha’s booking reaches a berth number. Step through the chain for Meena, whose booking is waitlisted, then for Asha, for Ravi and for a PNR that is not in the map:
The drawing shows the whole scene; its 8 steps are listed below.
Every step as text
- Meena's PNR. For Meena's PNR, 8240117367, the map lookup finds a booking. The value on the left of the first ?. is not null, so the chain goes on to read the coach.
- No coach yet. Meena is waitlisted, so her booking has no coach yet, and ?.coach gives null.
- The chain stops. The next ?. finds null on its left, so berth and number are never read and the whole chain is null. Berth.kt printed 8240117367 -> null.
- Asha's PNR. Asha's PNR, 8240117365, starts again from the map lookup, which finds her booking.
- Every link is there. Her booking has coach B2, and the coach holds her berth, so each ?. finds a value on its left and the chain goes on.
- A number at the end. ?.number reads 34, the value of the whole chain. Its type is still Int?, because the compiler cannot know which bookings will be complete.
- Ravi's PNR. Ravi's booking, 8240117366, has coach S5 but no berth yet, so his chain gets one link further than Meena's and stops at ?.berth. It is null too.
- An unknown PNR. For 8240117399 the map has no entry, so bookings[pnr] is null and no member after it is read. Three of the four PNRs end in the same null, for three different reasons.
A chain stops at its first null: once ?.coach gives null, berth and number are never read, and the whole
chain is null. Each safe call has a nullable result, so the next link needs ?. as well, and the chain as a
whole is an Int?. That is also its weakness. Three of the four PNRs printed the same null, and a caller of
berthNumber cannot tell an unknown PNR from a waitlisted booking.
Checkpoint
Check: Safe calls and where a chain stops
The Elvis operator supplies the value for null
x ?: y is the Elvis operator: The ?: operator: x ?: y gives x when x is not null and evaluates y only when x is null. The right side can be a fallback value, return or throw.. It gives x when x is not null, and works out
y only when x is null. The right side can be a fallback value, and it can also be return or throw, which are
expressions in Kotlin, so a function can stop at the first missing piece and say which one it was:
// The same bookings, now with a message for each way a berth can be missing.
class Berth(val number: Int, val kind: String)
class Coach(val code: String, val berth: Berth?)
class Booking(val passenger: String, val coach: Coach?)
val bookings: Map<String, Booking> = mapOf(
"8240117365" to Booking("Asha", Coach("B2", Berth(34, "lower"))),
"8240117366" to Booking("Ravi", Coach("S5", null)),
"8240117367" to Booking("Meena", null),
)
// Each ?: return ends the function early, and after it the name on the left is not null.
fun berthLabel(pnr: String): String {
val booking = bookings[pnr] ?: return "$pnr: no such booking"
val coach = booking.coach ?: return "${booking.passenger}: waitlisted, no coach yet"
val berth = coach.berth ?: return "${booking.passenger}: coach ${coach.code}, berth not allotted yet"
return "${booking.passenger}: coach ${coach.code}, berth ${berth.number} ${berth.kind}"
}
fun passengerOf(pnr: String): String = bookings[pnr]?.passenger ?: throw IllegalArgumentException("unknown PNR $pnr")
fun noBerth(): String {
println(" (noBerth() runs)")
return "no berth"
}
fun main() {
for (pnr in listOf("8240117365", "8240117366", "8240117367", "8240117399")) {
println(berthLabel(pnr))
}
// The right side of ?: runs only when the left side is null.
for (pnr in listOf("8240117365", "8240117367")) {
println(bookings[pnr]?.coach?.berth?.kind ?: noBerth())
}
println(passengerOf("8240117366"))
try {
passengerOf("8240117399")
} catch (e: IllegalArgumentException) {
println("caught: ${e.message}")
}
} Output
Asha: coach B2, berth 34 lower Ravi: coach S5, berth not allotted yet Meena: waitlisted, no coach yet 8240117399: no such booking lower (noBerth() runs) no berth Ravi caught: unknown PNR 8240117399
Recorded with Kotlin 2.4.20 on OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: kotlinc Elvis.kt -include-runtime -d main.jar && java -jar main.jar
The four PNRs now get four different answers. After the first line of berthLabel, booking has the type Booking,
not Booking?: the line returns early whenever the lookup gives null, so the next line can read booking.coach
with a plain dot. In the second loop, noBerth() ran once, for Meena, and not for Asha, whose berth was there: the
right side is evaluated only when the left side is null. passengerOf ends with ?: throw, and its exception carries
the PNR.
On the right of ?: |
Use it when | In Elvis.kt |
|---|---|---|
| a value | a missing value has a sensible default | ?: noBerth() |
return |
the function has nothing more to do without the value | ?: return "$pnr: no such booking" |
throw |
a missing value is a mistake to report | ?: throw IllegalArgumentException(…) |
Key idea
x ?: y gives x unless it is null, and runs y only then; with return or throw on the right, everything
after the line can treat the name on the left as not null.
Checkpoint
Check: The Elvis operator, with return and throw
A null check or let runs code only when a value is there
Sometimes there is no fallback to give: you want to run some code when a value is there, and nothing when it is not.
Two ways do that, an if that compares with null and ?.let:
// Two ways to run code only when a value is there: an if that checks for null, and ?.let.
fun handleOf(nickname: String): String? = if (nickname == "Ash") null else "@$nickname"
fun main() {
for (nickname in listOf("Ash", null)) {
if (nickname != null) {
// Inside the if, nickname is a String: no ? is needed for length.
println("$nickname has ${nickname.length} letters")
} else {
println("no nickname")
}
// let runs its block only when nickname is not null; inside it, the value is called it.
val greeting = nickname?.let { "Hello, $it!" }
println("greeting: $greeting")
// A trap: ?: also replaces a null that the block itself returns.
val handle = nickname?.let { handleOf(it) } ?: "no nickname"
println("handle: $handle")
}
} Output
Ash has 3 letters greeting: Hello, Ash! handle: no nickname no nickname greeting: null handle: no nickname
Recorded with Kotlin 2.4.20 on OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: kotlinc LetCheck.kt -include-runtime -d main.jar && java -jar main.jar
Inside if (nickname != null), the compiler treats nickname as a String, so nickname.length needs no ?. This
is a smart cast: The compiler's own cast: after a check such as x != null or x is String, it treats x as the checked type wherever the check still holds, with no cast written. More in “Smart casts, is checks and safe casts”, the subject of the next lesson. nickname?.let { … } calls let only
when nickname is not null; inside the block the value is called it and is a String, and the whole expression
gives the block’s last value, or null when the block was skipped. That is why greeting printed null the second
time.
Putting ?: after a let block that can return null
nickname?.let { handleOf(it) } ?: "no nickname" printed “no nickname” for Ash, who has a nickname: handleOf
returned null, and ?: replaces every null on its left, whichever part made it. When the block can give null,
write the two cases out with an if instead.
Prefer the if when there is an else to write, and ?.let for one short action or one transformation of a value. A
?.let nested inside another ?.let is harder to read than the early returns of berthLabel.
Checkpoint
Check: A null check, and ?.let
The not-null assertion throws when the value is missing
x!! is the not-null assertion: The !! operator: x!! gives x as a non-null type, and throws a NullPointerException without a message when x is null.: it gives x as a non-null type when x is not
null, and throws a NullPointerException when it is. Here is the berth lookup written with it:
// The berth lookup written with !! instead of safe calls.
class Berth(val number: Int, val kind: String)
class Coach(val code: String, val berth: Berth?)
class Booking(val passenger: String, val coach: Coach?)
val bookings: Map<String, Booking> = mapOf(
"8240117365" to Booking("Asha", Coach("B2", Berth(34, "lower"))),
"8240117367" to Booking("Meena", null),
)
fun berthNumber(pnr: String): Int = bookings[pnr]!!.coach!!.berth!!.number
fun main() {
println("8240117365 -> ${berthNumber("8240117365")}")
println("8240117367 -> ${berthNumber("8240117367")}")
} Output (exit status 1)
8240117365 -> 34
Printed as an error (standard error)
Exception in thread "main" java.lang.NullPointerException at BangKt.berthNumber(Bang.kt:13) at BangKt.main(Bang.kt:17) at BangKt.main(Bang.kt)
Recorded with Kotlin 2.4.20 on OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: kotlinc Bang.kt -include-runtime -d main.jar && java -jar main.jar
Asha’s lookup works. Meena’s stops the program with a NullPointerException that has no message at all, and the
stack trace points at line 13, in berthNumber. That line holds three !!, so the trace cannot say which value was
missing; here it was the coach, because Meena’s booking exists but is waitlisted. Every !! promises the compiler that
a value is never null, and a broken promise costs a crash with no explanation.
When a missing value really is a mistake, say so with requireNotNull, which returns the value as a non-null type, or
throws an IllegalArgumentException with your message:
// requireNotNull gives the missing value a name before the program stops.
class Booking(val passenger: String, val coach: String?)
val bookings: Map<String, Booking> = mapOf(
"8240117365" to Booking("Asha", "B2"),
"8240117367" to Booking("Meena", null),
)
fun bookingFor(pnr: String): Booking = requireNotNull(bookings[pnr]) { "no booking for PNR $pnr" }
fun main() {
println(bookingFor("8240117365").passenger)
println(bookingFor("8240117367").coach ?: "no coach yet")
println(bookingFor("8240117399").passenger)
} Output (exit status 1)
Asha no coach yet
Printed as an error (standard error)
Exception in thread "main" java.lang.IllegalArgumentException: no booking for PNR 8240117399 at RequireKt.bookingFor(Require.kt:9) at RequireKt.main(Require.kt:14) at RequireKt.main(Require.kt)
Recorded with Kotlin 2.4.20 on OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: kotlinc Require.kt -include-runtime -d main.jar && java -jar main.jar
The log now names the PNR. The two kinds of missing value need different tools, and two questions about the value decide which one fits. Answer them in the table:
Answer the questions above: the table then marks what fits your case and says why the rest does not.
| Way | The program goes on | Says what was missing | The result is not null |
|---|---|---|---|
| Safe call Written as: a?.b, the result may be null | Good the chain gives null and the code carries on | Poor Berth.kt printed the same null for three different gaps | Poor an Int?, so every caller has to decide again |
| Elvis with a value Written as: a ?: fallback | Good the fallback takes the place of null | Mixed only if the fallback says it, as "no berth" did | Good the fallback gives a value of the non-null type |
| Elvis with return Written as: a ?: return … | Mixed this function stops early; its caller carries on | Good berthLabel gave the four PNRs four different answers | Good after the line, the name on the left is not null |
| requireNotNull Written as: requireNotNull(a) { "…" } | Poor throws IllegalArgumentException | Good the message named PNR 8240117399 | Good returns the value as a non-null type |
| Not-null assertion Written as: a!! | Poor throws NullPointerException | Poor no message, and line 13 of Bang.kt held three !! | Good gives a non-null type when it does not throw |
How the answers decide
- “Is a missing value a normal case here, such as a waitlisted booking?” Yes, it happens in normal use: rules out requireNotNull and Not-null assertion. An exception for a normal case turns everyday data, such as a waitlisted booking, into a crash or an error in the log.
- “Is a missing value a normal case here, such as a waitlisted booking?” No, it can only be a mistake: rules out Not-null assertion and adds a caveat to Safe call and Elvis with a value. A mistake should be reported with its cause: requireNotNull names it, !! throws without a message, and a fallback hides it.
- “When the value is missing, what should this code do?” Use a default and go on: rules out Safe call and Elvis with return. A default belongs on the right of ?:, and a safe call on its own leaves the null for the caller.
- “When the value is missing, what should this code do?” Stop this function with an answer: rules out Safe call and Elvis with a value. ?: return ends the function at the first missing value and can say which one it was, as berthLabel does.
- “When the value is missing, what should this code do?” Give null back to the caller: rules out Elvis with a value, Elvis with return, requireNotNull and Not-null assertion. Only a safe call keeps the null, and its nullable result type tells the caller that the value may be missing.
In short: a safe call when the caller should decide, ?: with a value or return when a missing value is a normal
case with an answer of its own, requireNotNull with a message when it is a mistake, and !! almost never.
Checkpoint
Check: !! and requireNotNull
A nullable Boolean needs an explicit comparison
A form’s yes-or-no question has three answers once a passenger can skip it: true, false and null. An if needs a
Boolean, so a Boolean? cannot be its condition:
// A Boolean? cannot be the condition of an if: it has a third value, null.
fun main() {
val senior: Boolean? = listOf(true, false, null).last()
if (senior) {
println("concession fare")
}
} Output (exit status 1)
Printed as an error (standard error)
BooleanError.kt:4:9: error: condition type mismatch: inferred type is 'Boolean?' but 'Boolean' was expected.
if (senior) {
^^^^^^
Recorded with Kotlin 2.4.20 on OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: kotlinc BooleanError.kt -include-runtime -d main.jar && java -jar main.jar
The coding conventions say to compare with == true or == false instead, which gives a plain Boolean, because
null == true is false. Or decide what a skipped answer means, with ?: false:
// A form asks "Senior citizen?", and a passenger can answer yes, answer no or leave it blank.
fun main() {
val answers: List<Boolean?> = listOf(true, false, null)
for (senior in answers) {
val fare = if (senior == true) "concession fare" else "full fare"
val askAgain = senior == null
// != false looks like "yes", but it is also true for a blank answer.
val wrongCheck = senior != false
println("senior = $senior: $fare, ask again: $askAgain, as Boolean: ${senior ?: false}, != false: $wrongCheck")
}
} Output
senior = true: concession fare, ask again: false, as Boolean: true, != false: true senior = false: full fare, ask again: false, as Boolean: false, != false: false senior = null: full fare, ask again: true, as Boolean: false, != false: true
Recorded with Kotlin 2.4.20 on OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: kotlinc NullableBoolean.kt -include-runtime -d main.jar && java -jar main.jar
senior == true gives the concession only to a passenger who said yes, a skipped answer pays the full fare, and
senior == null finds the passengers to ask again. Avoid senior != false for “said yes”: the last column shows that
it is true for a skipped answer too.
Checkpoint
Check: A nullable Boolean in a condition
Practise: a display name for every user
This program checks a function that picks the name to show for a user: the nickname, else the first name, else
“Guest”. As given, it uses !!, and two of its five checks fail:
// Practice: fix displayName so that all five checks pass, without using !!.
// It must return the nickname, else the first name, else "Guest".
class User(val nickname: String?, val firstName: String?)
fun displayName(user: User?): String = user!!.nickname ?: user.firstName!!
fun describe(user: User?): String = if (user == null) "null" else "User(${user.nickname}, ${user.firstName})"
fun main() {
val checks = listOf(
User("Ash", "Asha") to "Ash",
User(null, "Ravi") to "Ravi",
User("Mee", null) to "Mee",
User(null, null) to "Guest",
null to "Guest",
)
var passed = 0
for ((user, want) in checks) {
val got = try {
displayName(user)
} catch (e: NullPointerException) {
"NullPointerException"
}
val ok = got == want
if (ok) passed++
println("${if (ok) "ok " else "FAIL"} displayName(${describe(user)}) = $got, want $want")
}
println("$passed of ${checks.size} checks pass")
if (passed < checks.size) kotlin.system.exitProcess(1)
} Output (exit status 1)
ok displayName(User(Ash, Asha)) = Ash, want Ash ok displayName(User(null, Ravi)) = Ravi, want Ravi ok displayName(User(Mee, null)) = Mee, want Mee FAIL displayName(User(null, null)) = NullPointerException, want Guest FAIL displayName(null) = NullPointerException, want Guest 3 of 5 checks pass
Recorded with Kotlin 2.4.20 on OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: kotlinc DisplayName.kt -include-runtime -d main.jar && java -jar main.jar
Copy it, rewrite displayName without !!, and run it with
kotlinc DisplayName.kt -include-runtime -d name.jar && java -jar name.jar until all five checks pass. First hint: one
expression is enough, with a safe call on user for each name and the Elvis operator between them.
Then work through the questions below on safe calls, the Elvis operator and the rest of this lesson. They begin where your answers so far have been weakest, and each one explains its choices:
Practice that adapts to you
Practise this lesson
10 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher · Warm-up
What is the difference between a?.b and a!!.b in Kotlin, and which should you reach for first?
Both read
bfrom a valueawhose type is nullable.a?.bis a safe call: whenais null it givesnullinstead of readingb, so the result has a nullable type and the program goes on.a!!.buses the not-null assertion: whenais null it throws aNullPointerExceptionwithout a message, and otherwise it treatsaas non-null. Reach for the safe call first, usually with the Elvis operator after it to say what a missing value means, as ina?.b ?: fallback, or?: returnto stop the function. Keep!!for a value that can only be null through a bug, and even thererequireNotNull(a) { "…" }is better, because its exception says what was missing. -
Fresher to mid
A crash report from a booking app shows a NullPointerException with no message, on the line
val berth = bookings[pnr]!!.coach!!.berth!!.number. How do you find out which value was null, and how would you rewrite the line so that the next failure explains itself? -
Mid to senior
A function findCustomer(id) sometimes finds nothing. Should it return null, a default customer or throw? Decide for two callers: a checkout API that charges the customer, and a nightly report that counts orders per city.
Key takeaways
Stringnever holdsnullandString?may; the compiler goes by the declared type, so aString?cannot go where aStringis expected, andx.lengthon aString?is refused.- A safe call
a?.bgivesnullwhenais null and skips the rest of a chain, sobookings[pnr]?.coach?.berth?.numberis anInt?. - The Elvis operator
x ?: yworks outyonly whenxis null;?: returnand?: throwend a function at the first missing value and leave the name on the left with a non-null type. if (x != null)andx?.let { … }run code only when a value is there, and a?:afterletalso replaces anullthat the block returns.x!!throws aNullPointerExceptionwithout a message whenxis null, whilerequireNotNull(x) { "…" }names what was missing.- A
Boolean?in a condition needs== true,== falseor?: false.
Terms in this lesson
- Elvis operator
- The ?: operator: x ?: y gives x when x is not null and evaluates y only when x is null. The right side can be a fallback value, return or throw.
- not-null assertion
- The !! operator: x!! gives x as a non-null type, and throws a NullPointerException without a message when x is null.
- nullable type
- A type written with ? after it, such as String?, whose values are those of the type plus null. The compiler refuses a plain call on it until the code says what happens when the value is null.
- safe call
- The ?. operator: a?.b reads b only when a is not null and gives null otherwise, so a chain of safe calls stops at its first null instead of throwing.
References
- Null safety (JetBrains (kotlinlang.org))
- Nullability in Java and Kotlin (JetBrains (kotlinlang.org))
- Idioms: if-not-null shorthands (JetBrains (kotlinlang.org))
- Scope functions: let (JetBrains (kotlinlang.org))
- Map-specific operations: retrieve keys and values (JetBrains (kotlinlang.org))
- Exceptions: precondition functions (JetBrains (kotlinlang.org))
- requireNotNull (Kotlin standard library) (JetBrains (kotlinlang.org))
- Coding conventions: nullable Boolean values in conditions (JetBrains (kotlinlang.org))
Report a problem with this lesson
Kept only in this browser. Your Learn progress