Course outline

Kotlin / Language foundations and explicit state / Lesson 3

Represent absence with nullable types

Use smart casts, safe calls, Elvis expressions, and nullable parsing without replacing domain meaning with unchecked assertions.

About 4 min readContent reviewed
On this page

Lesson goals

  • Distinguish a missing value, an empty string, and an invalid external value.
  • Choose explicit fallback or early return rather than unnecessary not-null assertions.

Nullability is part of the declared type

String promises a non-null string under Kotlinโ€™s type rules, while String? permits a string or null. Absence is different from an empty string: an empty value is present and has length zero. Choose the representation according to the domain, such as an optional nickname or a missing lookup result. Null safety catches many mistakes at compile time but is not a guarantee against every runtime null problem, especially at Java interoperability and initialization boundaries. Do not advertise Kotlin as making all null-related failures impossible.

A safe call preserves possible absence

The safe-call operator invokes a member only when its receiver is non-null and otherwise produces null. Chaining safe calls can model optional paths, but a long chain may hide which missing value matters. Elvis supplies a fallback only when the expression on its left is null, not when a string is empty or a number is zero. The example makes those distinctions visible with fixed values. Expected output is reasoned from language semantics, and no compiler or platform test was performed in this environment.

kotlin Distinguish absent and empty values
fun main() {
    val missing: String? = null
    val empty: String? = ""
    println(missing?.length ?: -1)
    println(empty?.length ?: -1)
    println(missing ?: "anonymous")
}
Expected output
-1
0
anonymous

Expected output is -1, 0, anonymous. The empty string is non-null, so its length is zero and Elvis does not choose -1. No program execution is claimed.

Let stable evidence narrow the type

After a suitable null check, Kotlin can smart-cast a stable value to a non-null type. Stability matters: a mutable property with a custom getter or an externally changeable value may not keep the same result between checks. Store a value in a local val when that snapshot is the intended meaning. Do not suppress a smart-cast failure with !! merely to satisfy the compiler; inspect whether the source can actually change. A successful check and later access need to refer to the same value under the assumptions of the code.

Use absence or a structured result for expected parse failure

toIntOrNull returns null when the text cannot be represented as an Int, including invalid syntax or an out-of-range value. A successfully parsed negative integer can still violate a seat-count rule, so parsing and domain validation are distinct steps. The function below uses an early return for invalid syntax and an explicit range check for meaning. Returning null for both reasons is acceptable only when callers do not need to distinguish them. Later sealed-result modeling can preserve detailed causes without using exceptions as normal branch control.

kotlin Separate parse success from domain validity
fun positiveSeats(text: String): Int? {
    val value = text.toIntOrNull() ?: return null
    return if (value > 0) value else null
}

fun main() {
    for (text in listOf("3", "bad", "0")) {
        println(positiveSeats(text) ?: "invalid")
    }
}
Expected output
3
invalid
invalid

Expected output is 3, invalid, invalid. Zero parses successfully but fails the positive-seat rule. All input strings are fixed teaching data and no interactive input is read.

Use not-null assertions only with a real invariant

The !! operator converts a nullable expression into a non-null expectation and throws if the value is null. It transfers responsibility to the programmer; it does not validate external data safely. Prefer an explicit rejection, fallback, or early return where absence is normal. At an integration boundary, decide whether missing data means not found, unavailable, malformed, or programmer misuse. Giving every case the same default can conceal a serious defect, while throwing on ordinary absence can make a predictable workflow unnecessarily fragile.

Common errors and fixes

A not-null assertion throws on missing input.

Why it happens
Expected absence was treated as an impossible state.
How to fix it
Handle null explicitly at the boundary.
Prevent it next time
Reserve assertions for justified internal invariants.

An empty nickname does not select the fallback.

Why it happens
Elvis tests null rather than blankness.
How to fix it
Apply an explicit blank-string rule if the domain requires it.
Prevent it next time
Test null, empty, whitespace, and ordinary text separately.

Parsed zero is accepted as a positive seat count.

Why it happens
Syntax conversion was mistaken for business validation.
How to fix it
Check the permitted range after parsing.
Prevent it next time
Keep parsing and domain constraints visible as distinct steps.

Check your understanding

1. Why is value?.length ?: 0 ambiguous for a report that must distinguish missing from empty?

Show worked solution

Both null and an empty string produce zero, so the output loses the distinction required by the report. Preserve a nullable length, a labeled state, or another explicit representation until presentation decides how to show each case. A convenient fallback is appropriate only when collapsing those meanings is intended.

2. What is wrong with text.toIntOrNull()!! for user-provided seats?

Show worked solution

Malformed or out-of-range text becomes a null assertion failure, and valid negative integers still pass the parsing stage. Use an explicit invalid-input outcome and then enforce the positive range. If callers need distinct error causes, return a structured result rather than discarding the reason.

Key takeaways

  • Nullable types make absence visible, but the domain defines its meaning.
  • Safe calls and Elvis do not replace validation or ownership.

Official references