Skip to content
Lesson 5 of 19

Step 1 of 5 · Reading · ~5 min

Learn

Strings and Null Safety

Null Safety

In most languages any reference can be null, so every method call is a coin flip that only lands at runtime. Kotlin's answer is blunt: null is part of the type. A String can never be null. A String? might be. The compiler knows which one you are holding and refuses to let you confuse them.

val city: String = "Oslo"
val nickname: String? = null

println(city.length)
// println(nickname.length)   // does not compile

That commented line is not a warning you can shrug off. It is a compile error — and that is the whole idea. The crash that would have happened in production happens on your machine instead.

The four tools

ToolWrittenWhat you get when the value is null
Null checkif (x != null) x.lengthyou handle the branch yourself
Safe callx?.lengththe whole expression becomes null
Elvisx?.length ?: 0the fallback you supplied
Assertionx!!.lengtha thrown NullPointerException

Read that table as a preference order. The first three are safe. The fourth is a promise you are making to the compiler, and the compiler will hold you to it.

Rendering diagram…

Smart casts: the check is enough

Once you have tested for null, the compiler remembers. Inside the branch the type is the non-nullable one, and you use the value normally — no cast, no unwrapping, no ceremony.

fun describe(label: String?): String {
    if (label == null) return "no label"
    return "label has " + label.length + " characters"
}

fun main() {
    println(describe("kotlin"))
    println(describe(null))
}

//> label has 6 characters
//> no label

After that early return, label is a plain String for the rest of the function. That is a smart cast: you proved it, so the compiler upgrades the type for you.

Safe call and Elvis, side by side

fun main() {
    val note: String? = null

    println(note?.length)
    println(note?.length ?: 0)
    println(note?.toUpperCase() ?: "(none)")
}

//> null
//> 0
//> (none)

A safe call is contagious. note?.length is not an Int, it is an Int?, because it might have produced nothing. Chain several of them and the whole chain collapses to null the moment any link is null.

Elvis is how you climb back out to a normal type. Everything to the right of ?: runs only when the left side turned out to be null. It is not limited to plain values either — return and throw are expressions in Kotlin, so this is completely idiomatic:

val raw = readLine() ?: return

?.let — run a block only if there is something

fun main() {
    val maybe: String? = "hello"
    maybe?.let { println(it.length) }
}

//> 5

Had maybe been null, the block simply would not have run. Inside the block, it is the non-null value.

The !! operator, honestly

!! converts a T? into a T and throws NullPointerException if you were wrong. It exists, it is occasionally the right call, and it is also the most abused thing in the language.

✓ Defensible — the exercise guarantees an input line exists:

val n = readLine()!!.toInt()

✗ Not defensible — there is an obvious fallback sitting right there:

val name = readLine()!!
val name = readLine() ?: "anonymous"

Every starter in this course uses readLine()!! because our graders always supply the input the exercise describes. In code that faces the real world, prefer ?:, ?.let, or an explicit check. A habit worth forming: whenever you type !!, say out loud why the value cannot be null. If you cannot finish the sentence, you have found a bug.

Text that might not be a number

Conversions come in two flavours, and this is where nullable types earn their keep.

CallOn "42"On "abc"
.toInt()42throws NumberFormatException
.toIntOrNull()42returns null
val value = readLine()?.toIntOrNull() ?: 0

One line: read a line that might not exist, parse text that might not be a number, and fall back to zero if either goes wrong. That is the whole feature working as designed.

Your exercise

Length or Zero reads a single line and prints how many characters it holds.

The starter opens with readLine() ?: "" — Elvis doing exactly what the table above promises, turning a missing line into an empty string so the rest of your code deals with a plain, non-null String. The grader's second visible test sends an empty line and expects 0.

Two mistakes fail it. Keeping the value nullable and printing line?.length prints the word null instead of 0. And swapping the starter's ?: "" for !! throws a NullPointerException when there is no line at all, instead of printing a number. Print the count on its own line with no label.

Up nextErrors and try/catchStrings and Null Safety

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…