Skip to content
Errors and try/catch
step 1/7

Reading — step 1 of 7

Learn

~3 min readStrings and Null Safety

Errors and try/catch

This course has already told you about four things that throw. "4x".toInt() throws. list[5] throws. reduce on an empty list throws. !! on null throws. What it has not shown you is what to do about any of them.

fun main() {
    val good = try { "42".toInt() } catch (e: NumberFormatException) { -1 }
    val bad  = try { "4x".toInt() } catch (e: NumberFormatException) { -1 }
    println("$good $bad")

    //> 42 -1
}

Read that shape carefully, because it is the Kotlin-specific part: try is an expression. It has a value, so it can sit on the right of a val, exactly like the if and when expressions you already use. The value is the last line of whichever block ran.

What the exception carries

catch (e: NumberFormatException) binds the thrown object to e, and its message tells you what the input actually was:

fun main() {
    try {
        "4x".toInt()
    } catch (e: NumberFormatException) {
        println(e.message)
    }

    //> For input string: "4x"
}

Catching the right type

Each of those four failures throws a different type. Catching too broadly hides bugs; catching too narrowly misses the failure. The ones this course has already mentioned:

What you wroteWhat it throws
"4x".toInt()NumberFormatException
listOf(1, 2)[5]ArrayIndexOutOfBoundsException, a kind of IndexOutOfBoundsException
listOf<Int>().reduce { .. }UnsupportedOperationException
nullable!!KotlinNullPointerException, a kind of NullPointerException
require(false)IllegalArgumentException

Catching the parent type catches the child, which is why catch (e: IndexOutOfBoundsException) is the right net for a bad index even though the object is an ArrayIndexOutOfBoundsException.

finally

A finally block runs on the way out no matter which path was taken — value returned, exception caught, or exception still travelling:

fun probe(s: String): String =
    try { s.toInt(); "ok" } catch (e: NumberFormatException) { "bad" }
    finally { print("finally($s) ") }

fun main() {
    println(probe("7") + " " + probe("x"))

    //> finally(7) finally(x) ok bad
}

Often you do not need to catch at all

Kotlin's standard library usually offers a total function instead of a throwing one, and reaching for it is the more idiomatic choice:

fun main() {
    println("4x".toIntOrNull())
    println(runCatching { "4x".toInt() }.isFailure)

    //> null
    //> true
}

toIntOrNull() returns null rather than throwing, which hands the problem to the null-safety tools you already know. Use try/catch when the failure is genuinely exceptional, or when you need the message.

Common mistakes

  • Catching Exception for everything — it hides the bug you did not expect along with the one you did.
  • An empty catch block — the program continues in a state you never reasoned about.
  • Forgetting try is an expression — assigning in both branches works, but val x = try { .. } catch { .. } is shorter and cannot leave x unassigned.
  • Using try/catch where toIntOrNull() would do — parsing user input is expected to fail, so it is not exceptional.

Your exercise

Parse or Report reads a count, then that many lines. For each line, print ok: <value> when it parses as an Int, and bad: <message> when it does not — where <message> is the exception's own message, which for 4x reads For input string: "4x". Finish with sum: of the values that parsed and bad: of how many did not.

Because the failing lines must report the exception's own text, toIntOrNull() cannot do the job here — you need the caught object.

Discussion

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

Sign in to post a comment or reply.

Loading…