Free Handbook · Every example compiled & verified

Errors & Debugging

The 15 Kotlin errors beginners hit most, with the real compiler and JVM messages, how to read a stack trace, and how to debug and log.

0 / 136 lessons🔥 0 day streak
ShareXLinkedIn

Module 11 · what you'll be able to do

  • Tell a kotlinc compile error from a JVM runtime exception, and read each one in the right order
  • Fix the 15 errors Kotlin beginners hit most, from type mismatch and nullable receivers to ConcurrentModificationException
  • Read a stack trace bottom-up and find the first frame in your own code
  • Use require, check, runCatching and toIntOrNull to fail early or not at all
  • Debug with IntelliJ breakpoints, print debugging with onEach/also, logging, and static analysis
01

Compile errors vs runtime exceptions

Kotlin checks your program twice. First kotlinc, the compiler, reads every file and refuses to produce bytecode if the grammar, the types or the null-safety rules are wrong. Kotlin's compiler catches much more than most languages' — null dereferences, missing when branches, reassigned vals — so a large share of beginner bugs never reach runtime. What is left runs on the JVM, and problems that only appear with real data (an index past the end, text that is not a number, a !! on a null) are thrown as exceptions.

Compile error (kotlinc)

  • Nothing runs at all — not even the first line of main
  • Format: Main.kt:3:18: error: message (file, line, column), then the line with ^^^ under the exact token
  • In IntelliJ: red underline as you type; in Gradle: lines starting with e:
  • Always reproducible: same code, same error

Runtime exception (JVM)

  • The program started, maybe printed output, then stopped
  • Format: Exception in thread "main" java.lang.XxxException: message
  • Followed by at MainKt.function(Main.kt:line) frames
  • Depends on the input: it may work for one value and crash for another

Anatomy of a kotlinc error

text
Main.kt:3:18: error: unresolved reference 'size' on receiver of type 'String'.
    println(word.size)
                 ^^^^

File, line 3, column 18; the word error (warnings say warning); the message; then the source line with carets under the exact token. The message usually names both what you wrote and the type the compiler saw.

  • Fix the first error first. One missing brace or parenthesis can produce a cascade; fix the first, recompile, and many of the rest vanish.
  • Read the types in quotes. expected 'Int', actual 'String' or receiver of type 'String?' is usually the whole diagnosis.
  • Read warnings too. "Variable is never used", "Condition is always true" and "Unchecked cast" often point at the real bug. Many teams turn on allWarningsAsErrors in Gradle.

Anatomy of a stack trace

text
Exception in thread "main" java.lang.NumberFormatException: For input string: "x"
	at java.base/java.lang.NumberFormatException.forInputString(Unknown Source)
	at java.base/java.lang.Integer.parseInt(Unknown Source)
	at java.base/java.lang.Integer.parseInt(Unknown Source)
	at MainKt.parse(Main.kt:1)
	at MainKt.total(Main.kt:5)
	at MainKt.main(Main.kt:10)
	at MainKt.main(Main.kt)

The real trace of the program below without its try/catch. Top-level functions in Main.kt compile into a class called MainKt. The last frame, with no line number, is the JVM entry point Kotlin generates around your main.

  1. 1
    Read the first line

    The exception class and message are often the whole answer: For input string: "x" means someone converted "x" to a number.

  2. 2
    Skip the library frames

    Frames starting with java.base/, kotlin. or a framework package are not your bug. Kotlin's toInt() is a thin wrapper, so the JDK's Integer.parseInt appears here.

  3. 3
    Find the first frame in your code

    MainKt.parse(Main.kt:1) is the line of your code that made the failing call. Open it.

  4. 4
    Read downward to see how you got there

    Each frame below is the caller: total line 5 called parse, and main line 10 called total. The bad value came in along that path.

  5. 5
    Jump to the last "Caused by"

    Frameworks (Spring, Ktor, coroutines) wrap exceptions. The deepest Caused by: section is the original failure. Lambdas show up as frames like MainKt$main$1.invoke.

A stack trace is also an object you can inspect. This program catches the exception and prints only the frames from MainKt — exactly the filter your eyes should apply.

kotlinMain.kt
fun parse(s: String): Int = s.toInt()

fun total(items: List<String>): Int {
    var t = 0
    for (s in items) t += parse(s)
    return t
}

fun main() {
    try {
        total(listOf("4", "x"))
    } catch (e: NumberFormatException) {
        println(e)
        for (f in e.stackTrace) {
            if (f.className == "MainKt" && f.lineNumber > 0) {
                println("  at ${f.methodName} line ${f.lineNumber}")
            }
        }
    }
}
Outputcompiled & run with real Kotlin
java.lang.NumberFormatException: For input string: "x"
  at parse line 1
  at total line 5
  at main line 11
Your turn

Swap the list to listOf("x", "4"). Do the frames change? Why not?

02

Compile errors: types, names and nulls

The first four errors are the compiler enforcing Kotlin's type system. Each one is a bug that would crash — or silently misbehave — in a looser language.

Error you will hit

1. Type mismatch

kotlin
fun main() {
    val count: Int = "42"
    println(count + 1)
}
Main.kt:2:20: error: initializer type mismatch: expected 'Int', actual 'String'.
    val count: Int = "42"
                   ^
Why the compiler said that

Kotlin never converts between types for you. "42" is text; an Int variable only accepts a number. The same family of error appears as argument type mismatch for function arguments and return type mismatch for returns.

The fix

Convert explicitly — "42".toInt() (or toIntOrNull() for user input) — or change the declared type if text is what you meant.

kotlin
fun main() {
    val count: Int = "42".toInt()
    println(count + 1)
}
Error you will hit

2. Unresolved reference

kotlin
fun main() {
    val word = "kotlin"
    println(word.size)
}
Main.kt:3:18: error: unresolved reference 'size' on receiver of type 'String'.
    println(word.size)
                 ^^^^
Why the compiler said that

The compiler cannot find anything called size on a String. Strings have length; collections have size. The same error appears for typos, wrong capitalisation, a missing import, or a variable used outside the block that declared it.

The fix

Use the right name (word.length). In IntelliJ, type the dot and read the completion list, or press Alt+Enter on the red name to add a missing import.

kotlin
fun main() {
    val word = "kotlin"
    println(word.length)
}
Error you will hit

3. Only safe (?.) or non-null asserted (!!.) calls are allowed

kotlin
fun main() {
    val input: String? = readlnOrNull()
    println(input.trim())
}
Main.kt:3:18: error: only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type 'String?'.
    println(input.trim())
                 ^
Why the compiler said that

readlnOrNull() returns null at end of input, so input is String?. Calling trim() on null would crash, so the compiler makes you decide what happens in that case.

The fix

Provide a default with ?:, use a safe call ?., or check for null first. Reaching for !! just moves the crash to runtime (error 8). See Null Safety.

kotlin
fun main() {
    val input: String = readlnOrNull() ?: ""
    println(input.trim())
}
Error you will hit

4. 'val' cannot be reassigned

kotlin
fun main() {
    val total = 0
    for (n in listOf(1, 2, 3)) {
        total += n
    }
    println(total)
}
Main.kt:4:9: error: 'val' cannot be reassigned.
        total += n
        ^^^^^
Why the compiler said that

val is a read-only reference, assigned exactly once. total += n means total = total + n, a second assignment.

The fix

Use var for a running total — or, more idiomatically, no mutable variable at all: listOf(1, 2, 3).sum().

kotlin
fun main() {
    var total = 0
    for (n in listOf(1, 2, 3)) {
        total += n
    }
    println(total)
}
03

Compile errors: when, smart casts and returns

The next three come from the compiler proving things about every path through your code: that a when handles every case, that a value cannot change between a check and its use, and that a function always returns.

Error you will hit

5. 'when' expression must be exhaustive

kotlin
enum class Status { ACTIVE, SUSPENDED, CLOSED }

fun label(s: Status): String = when (s) {
    Status.ACTIVE -> "ok"
    Status.SUSPENDED -> "paused"
}

fun main() = println(label(Status.CLOSED))
Main.kt:3:32: error: 'when' expression must be exhaustive. Add the 'CLOSED' branch or an 'else' branch.
fun label(s: Status): String = when (s) {
                               ^^^^
Why the compiler said that

A when used as a value must produce one for every possible input. The compiler knows every constant of the enum, sees that CLOSED is missing, and names it. This check is a feature: when someone adds a new enum constant or sealed subclass, every when that forgot it stops compiling.

The fix

Add the missing branch. Prefer listing every case over else for enums and sealed types, so the next new case is caught too.

kotlin
enum class Status { ACTIVE, SUSPENDED, CLOSED }

fun label(s: Status): String = when (s) {
    Status.ACTIVE -> "ok"
    Status.SUSPENDED -> "paused"
    Status.CLOSED -> "closed"
}

fun main() = println(label(Status.CLOSED))
Error you will hit

6. Smart cast is impossible

kotlin
fun main() {
    var name: String? = "ash"
    val reset = { name = null }
    if (name != null) {
        reset()
        println(name.length)
    }
}
Main.kt:6:17: error: smart cast to 'String' is impossible, because 'name' is a local variable that is mutated in a capturing closure.
        println(name.length)
                ^^^^
Why the compiler said that

After name != null the compiler would normally treat name as String. But a lambda can change it, and here one does: reset() sets it back to null between the check and the use. The same error appears for var properties of a class and for open or custom-getter properties.

The fix

Copy the value into a local val and check that, or use name?.let { … }, which reads it once. Better still, avoid mutating captured variables.

kotlin
fun main() {
    var name: String? = "ash"
    val reset = { name = null }
    val current = name
    if (current != null) {
        reset()
        println(current.length)
    }
}
Error you will hit

7. Missing return statement

kotlin
fun grade(score: Int): String {
    if (score >= 90) return "A"
    else if (score >= 75) return "B"
}

fun main() = println(grade(80))
Main.kt:4:1: error: missing return statement.
}
^
Why the compiler said that

A function with a block body and a declared return type must return on every path. When score is below 75, neither branch runs and the function would fall off the end with nothing to return. The caret points at the closing brace — the place where the missing return would have to be.

The fix

Cover every path. In Kotlin the cleanest way is an expression body with when, where a missing else is itself a compile error.

kotlin
fun grade(score: Int): String = when {
    score >= 90 -> "A"
    score >= 75 -> "B"
    else -> "C"
}

fun main() = println(grade(80))
Let the compiler work for you
Every error on this page so far is a crash that never happened. Lean into it: prefer val, expression-bodied when over if/else chains, sealed types over strings and booleans. The more the compiler can prove, the fewer of the next eight you will see.
04

Runtime exceptions: nulls, lateinit and casts

These compiled cleanly because you told the compiler to trust you — with !!, lateinit or as. At runtime the promise turned out to be false.

Error you will hit

8. NullPointerException from !!

kotlin
fun main() {
    val prices = mapOf("tea" to 20, "coffee" to 35)
    val price = prices["juice"]!!
    println(price * 2)
}
Exception in thread "main" java.lang.NullPointerException
	at MainKt.main(Main.kt:3)
	at MainKt.main(Main.kt)
Why the compiler said that

Looking up a missing key in a map returns null, so prices["juice"] is Int?. !! promises it is not null and throws a bare NullPointerException when the promise is wrong — with no message to say which value was missing.

The fix

Handle the missing case: prices["juice"] ?: 0, getOrElse, or getValue("juice"), which throws NoSuchElementException naming the key. If null is truly a bug, requireNotNull(x) { "reason" } fails with a message.

kotlin
fun main() {
    val prices = mapOf("tea" to 20, "coffee" to 35)
    val price = prices["juice"] ?: 0
    println(price * 2)
}
Error you will hit

9. lateinit property has not been initialized

kotlin
class Screen {
    lateinit var title: String

    fun show() = println(title.uppercase())
}

fun main() {
    Screen().show()
}
Exception in thread "main" kotlin.UninitializedPropertyAccessException: lateinit property title has not been initialized
	at Screen.getTitle(Main.kt:2)
	at Screen.show(Main.kt:4)
	at MainKt.main(Main.kt:8)
	at MainKt.main(Main.kt)
Why the compiler said that

lateinit promises the property is assigned before it is read. Nothing assigned it. In real projects this means a dependency-injection setup, a test's @BeforeEach, or an Android onCreate did not run in the order you assumed.

The fix

Assign it before use, check ::title.isInitialized, or — if it can legitimately be missing — make it a nullable property or a constructor parameter instead.

kotlin
class Screen(val title: String) {
    fun show() = println(title.uppercase())
}

fun main() {
    Screen("home").show()
}
Error you will hit

10. ClassCastException

kotlin
fun main() {
    val json: Map<String, Any> = mapOf("name" to "Asha", "age" to "31")
    val age = json["age"] as Int
    println(age + 1)
}
Exception in thread "main" java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer (java.lang.String and java.lang.Integer are in module java.base of loader 'bootstrap')
	at MainKt.main(Main.kt:3)
	at MainKt.main(Main.kt)
Why the compiler said that

as is an unchecked promise about the type. The value is the text "31", and a cast never converts text to a number. This is common with parsed JSON, where numbers sometimes arrive as strings.

The fix

Use as? with a fallback when the type is uncertain, and convert explicitly when you want parsing. Better: parse JSON into a data class with a library (kotlinx.serialization, Jackson), so types are checked once at the edge.

kotlin
fun main() {
    val json: Map<String, Any> = mapOf("name" to "Asha", "age" to "31")
    val age = (json["age"] as? String)?.toIntOrNull() ?: 0
    println(age + 1)
}
05

Runtime exceptions: collections, parsing and recursion

Error you will hit

11. IndexOutOfBoundsException from 0..size

kotlin
fun main() {
    val scores = listOf(70, 85, 90)
    for (i in 0..scores.size) {
        println(scores[i])
    }
}
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
	at java.base/java.util.Arrays$ArrayList.get(Unknown Source)
	at MainKt.main(Main.kt:4)
	at MainKt.main(Main.kt)
Why the compiler said that

Indexes run from 0 to size - 1, but 0..scores.size includes size itself. The loop printed 70, 85 and 90, then asked for index 3. (listOf is backed by an array, so this one says ArrayIndexOutOfBoundsException; a mutableListOf says IndexOutOfBoundsException. Same bug.)

The fix

Loop over the elements directly, or use scores.indices or 0 until scores.size. For a single lookup that may miss, use getOrNull(i).

kotlin
fun main() {
    val scores = listOf(70, 85, 90)
    for (i in scores.indices) {
        println(scores[i])
    }
}
Error you will hit

12. NumberFormatException

kotlin
fun main() {
    val price = "12.5".toInt()
    println(price)
}
Exception in thread "main" java.lang.NumberFormatException: For input string: "12.5"
	at java.base/java.lang.NumberFormatException.forInputString(Unknown Source)
	at java.base/java.lang.Integer.parseInt(Unknown Source)
	at java.base/java.lang.Integer.parseInt(Unknown Source)
	at MainKt.main(Main.kt:2)
	at MainKt.main(Main.kt)
Why the compiler said that

toInt() only accepts whole numbers. "12.5", " 12" (a space) and "12a" all throw. It is the most common crash on user input and CSV files.

The fix

Pick the parser that matches the data (toDouble(), toBigDecimal() for money), trim() first, and use toIntOrNull() whenever the input comes from outside your program.

kotlin
fun main() {
    val price = "12.5".toDoubleOrNull() ?: 0.0
    println(price)
}
Error you will hit

13. ConcurrentModificationException

kotlin
fun main() {
    val ids = mutableListOf(1, 2, 3, 4)
    for (id in ids) {
        if (id == 1) ids.remove(id)
    }
    println(ids)
}
Exception in thread "main" java.util.ConcurrentModificationException
	at java.base/java.util.ArrayList$Itr.checkForComodification(Unknown Source)
	at java.base/java.util.ArrayList$Itr.next(Unknown Source)
	at MainKt.main(Main.kt:3)
	at MainKt.main(Main.kt)
Why the compiler said that

Despite the name, no threads are involved. The for loop's iterator notices the list changed underneath it and refuses to continue. Line 3 is blamed because the next step of the loop is where the check runs.

The fix

Use removeAll { it == 1 } (or removeIf), or build a new list with filter. If you must remove while iterating, use an explicit iterator and its remove().

kotlin
fun main() {
    val ids = mutableListOf(1, 2, 3, 4)
    ids.removeAll { it == 1 }
    println(ids)
}
Error you will hit

14. StackOverflowError

kotlin
fun countdown(n: Int): Int = n + countdown(n - 1)

fun main() {
    println(countdown(3))
}
Exception in thread "main" java.lang.StackOverflowError
	at MainKt.countdown(Main.kt:1)
	at MainKt.countdown(Main.kt:1)
	at MainKt.countdown(Main.kt:1)
	at MainKt.countdown(Main.kt:1)
Why the compiler said that

The recursion has no base case, so every call makes another call until the thread's stack is full. The real trace is the same countdown frame repeated about a thousand times (trimmed here); a trace like that is the signature of this bug. It is an Error, not an Exception: do not try to catch it.

The fix

Add a base case that stops the recursion. For deep but correct recursion, rewrite as a loop or mark a tail-recursive function tailrec (see Functions & Lambdas).

kotlin
fun countdown(n: Int): Int = if (n <= 0) 0 else n + countdown(n - 1)

fun main() {
    println(countdown(3))
}
Error you will hit

15. UnsupportedOperationException from casting a read-only list

kotlin
fun main() {
    val readOnly: List<Int> = listOf(1, 2, 3)
    val sneaky = readOnly as MutableList<Int>
    sneaky.add(4)
    println(sneaky)
}
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.base/java.util.AbstractList.add(Unknown Source)
	at java.base/java.util.AbstractList.add(Unknown Source)
	at MainKt.main(Main.kt:4)
	at MainKt.main(Main.kt)
Why the compiler said that

On the JVM, Kotlin's List and MutableList are both java.util.List, so the cast succeeds. But listOf returns a fixed-size list, and add is not supported. Read-only in the type became read-only for real.

The fix

Never cast a read-only collection to a mutable one. Make a mutable copy with toMutableList(), or build a new list with +.

kotlin
fun main() {
    val readOnly: List<Int> = listOf(1, 2, 3)
    val copy = readOnly.toMutableList()
    copy.add(4)
    println(copy)
}
06

Fail early with require and check, or not at all

Most of the runtime errors above crash far from the real mistake. The standard library has three one-liners that make a program fail at the right place with a useful message: require(condition) { message } for bad arguments (throws IllegalArgumentException), check(condition) { message } for bad object state (throws IllegalStateException), and error(message) for code that should be unreachable.

kotlinMain.kt
fun withdraw(balance: Int, amount: Int): Int {
    require(amount > 0) { "amount must be positive, was $amount" }
    check(balance >= amount) { "insufficient funds: balance $balance, amount $amount" }
    return balance - amount
}

fun main() {
    println(withdraw(100, 30))
    for (amount in listOf(-5, 500)) {
        try {
            withdraw(100, amount)
        } catch (e: IllegalArgumentException) {
            println("bad input: ${e.message}")
        } catch (e: IllegalStateException) {
            println("bad state: ${e.message}")
        }
    }
}
Outputcompiled & run with real Kotlin
70
bad input: amount must be positive, was -5
bad state: insufficient funds: balance 100, amount 500
Your turn

Add requireNotNull to a new function fun parseId(raw: String?): String and call it with null inside a try/catch.

The opposite strategy — for input you expect to be bad sometimes — is not to throw at all. The OrNull functions (toIntOrNull, getOrNull, firstOrNull) return null instead of throwing, and runCatching { } turns any exception into a Result value you can inspect.

kotlinMain.kt
fun main() {
    val inputs = listOf("42", "x", "7")

    val results = inputs.map { s -> runCatching { s.toInt() } }
    for (r in results) {
        println(r.fold({ "ok $it" }, { "failed: ${it::class.simpleName}" }))
    }

    println(inputs.mapNotNull { it.toIntOrNull() }.sum())
}
Outputcompiled & run with real Kotlin
ok 42
failed: NumberFormatException
ok 7
49

Prefer the OrNull version when one exists; use runCatching around calls that can fail in several ways. Inside coroutines, remember that runCatching also catches cancellation (Coroutines).

07

Debugger, print debugging, logging and linters

The error message tells you where a program failed. When it did not crash but gave the wrong answer, you need to see what it was thinking. Four tools, in the order you will reach for them.

1. The IntelliJ / Android Studio debugger

  1. 1
    Set a breakpoint

    Click the gutter next to a line number, then start with Debug (the bug icon) instead of Run. The program pauses when it reaches that line.

  2. 2
    Inspect

    The Variables panel shows every local and property. Hover over any expression, or press Alt+F8 (Evaluate Expression) to run Kotlin code against the paused program.

  3. 3
    Step

    Step Over (F8) runs the line. Step Into (F7) enters the function called on it. Step Out (Shift+F8) finishes the current function. For chains like list.map { }.filter { }, Smart Step Into (Shift+F7) lets you pick which lambda to enter.

  4. 4
    Conditional and exception breakpoints

    Right-click a breakpoint and add a condition such as id == 999. Add a Java Exception breakpoint for NullPointerException to pause at the exact moment one is thrown.

  5. 5
    Coroutines

    While paused, the Coroutines tab lists every live coroutine and its state (running, suspended), which is how you find one stuck forever in a receive().

2. Print debugging, done properly

A well-placed print is a legitimate tool. In Kotlin, collection pipelines make it tricky — there is no statement to put a print between — so use onEach { } (runs for every element and passes the collection on) and also { } (runs once with the whole value). Both return their input unchanged, so you can drop them into a chain and delete them later.

kotlinMain.kt
fun main() {
    val words = listOf(" Kotlin", "java ", "", "Go")
    val result = words
        .map { it.trim() }
        .onEach { println("after trim: '$it'") }
        .filter { it.length > 2 }
        .also { println("after filter: $it") }
        .map { it.lowercase() }
    println(result)
}
Outputcompiled & run with real Kotlin
after trim: 'Kotlin'
after trim: 'java'
after trim: ''
after trim: 'Go'
after filter: [Kotlin, java]
[kotlin, java]

The prints show where "Go" disappeared: the length filter, not the trim. Data classes help here too — their toString() prints every property.

Your turn

Change the filter to it.isNotEmpty() and predict both debug lines before running it.

3. Logging for code that runs without you

Prints are removed before commit. Logging stays: it has levels (debug detail can be switched off in production), timestamps and thread names, and it goes to files or a log platform. Kotlin projects use SLF4J with Logback, often through the small kotlin-logging wrapper, whose lambda messages are only built when that level is enabled.

kotlin
import io.github.oshai.kotlinlogging.KotlinLogging

private val log = KotlinLogging.logger {}

class OrderService(private val payments: Payments) {
    fun place(order: Order) {
        log.debug { "placing order ${order.id} with ${order.items.size} items" }  // built only if DEBUG is on
        try {
            payments.charge(order)
            log.info { "order ${order.id} placed" }
        } catch (e: PaymentException) {
            log.error(e) { "payment failed for order ${order.id}" }   // e first: the stack trace is logged
            throw e
        }
    }
}

Always pass the exception object to the logger. Logging only e.message throws away the stack trace you will need at 2 a.m.

4. Let tools find bugs before you run anything

  • IntelliJ inspections: yellow highlights for unused values, always-true conditions and redundant !!. Alt+Enter offers the fix.
  • detekt: static analysis for Kotlin — complexity, swallowed exceptions, magic numbers. Runs in Gradle and CI.
  • ktlint: formatting and style, so reviews talk about logic instead of spaces.
  • Kotlin scratch files and the REPL: in IntelliJ, File → New → Scratch File → Kotlin evaluates code as you type — the fastest way to answer "what does "a,b,,c".split(",") return?"
A debugging routine that works
1. Reproduce it with the smallest input. 2. Read the whole message and find your first frame. 3. Form one guess about the cause. 4. Check it with a breakpoint or a print. 5. Fix it, re-run the original failing case, and add a unit test (JUnit 5) so it never comes back.
08

Index of all 15 errors

Coroutine-specific errors (calling a suspend function from regular code, restricted suspension in sequence { }) are in Coroutines.
#ErrorKindUsual causeUsual fix
1type mismatchCompileA String where an Int is expected (or similar)Convert explicitly: toInt(), toString()
2unresolved referenceCompileTypo, wrong member (size vs length), missing importFix the name; Alt+Enter to import
3only safe (?.) or non-null asserted (!!.) calls…CompileCalling a member on a T??., ?: or a null check
4'val' cannot be reassignedCompileUpdating a valvar, or compute without mutation
5'when' expression must be exhaustiveCompileA missing enum or sealed caseAdd the branch (prefer it over else)
6smart cast … is impossibleCompileA value that could change between check and useCopy to a local val or use ?.let
7missing return statementCompileA path with no returnExpression body with when and else
8NullPointerExceptionRuntime!! on a null (or a Java platform type)?:, getValue, requireNotNull
9UninitializedPropertyAccessExceptionRuntimelateinit read before assignmentAssign earlier, or use a constructor parameter
10ClassCastExceptionRuntimeas to a type the value is notas? with a fallback; parse, do not cast
11IndexOutOfBoundsExceptionRuntime0..size instead of indicesindices, until, getOrNull
12NumberFormatExceptionRuntimeConverting text that is not a whole numbertoIntOrNull(), trim(), the right parser
13ConcurrentModificationExceptionRuntimeRemoving from a list inside its for loopremoveAll { } or filter
14StackOverflowErrorRuntimeRecursion with no base caseAdd a base case, a loop or tailrec
15UnsupportedOperationExceptionRuntimeCasting a read-only list to MutableListtoMutableList()
Compile error
A problem kotlinc finds before anything runs; no bytecode is produced.
Runtime exception
An exception thrown while the program runs, such as NullPointerException or NumberFormatException.
Stack trace
The exception type, message and the chain of function calls (frames) that led to it, newest first.
MainKt
The JVM class Kotlin generates for the top-level functions in Main.kt; it names those frames in a stack trace.
Caused by
A wrapped original exception inside a stack trace; the deepest one is the root cause.
require / check
Standard-library preconditions that throw IllegalArgumentException / IllegalStateException with your message.
runCatching
Runs a block and returns a Result holding either the value or the exception.
Breakpoint
A line where the debugger pauses the program so you can inspect variables.
detekt
A static-analysis tool for Kotlin that reports code smells and likely bugs.
Quick check

A trace shows at java.base/java.lang.Integer.parseInt(Unknown Source), then at MainKt.load(Main.kt:21), then at MainKt.main(Main.kt:5). Which line do you open first?

Quick check

Which of these is caught by the compiler rather than at runtime?

Frequently asked questions

Why does my Kotlin stack trace say MainKt instead of Main?
The JVM only runs classes, and Kotlin allows functions outside any class. The compiler puts the top-level functions of Main.kt into a generated class named MainKt, so a function parse in that file appears as MainKt.parse(Main.kt:1). Functions inside class Screen appear as Screen.show, and lambdas as MainKt$main$1.invoke.
Can Kotlin code still throw NullPointerException?
Yes, but only through a few doors: the !! operator, Java platform types assigned to non-null Kotlin types, and initialisation-order mistakes. Reading a lateinit property too early throws a separate, clearer UninitializedPropertyAccessException. Code that avoids !! and treats Java results as nullable almost never sees one.
Should I catch Exception everywhere to stop crashes?
No. Catch the specific exceptions you can handle (NumberFormatException on user input, IOException on the network) at the point where you can do something useful. A blanket catch (e: Exception) hides bugs, and in coroutines it also swallows cancellation. Let unexpected exceptions reach a top-level handler that logs them with the full stack trace.

Finish the Kotlin handbook, then get hired

Sit the exam for your certificate, run your resume through the ATS checker, and see the jobs that ask for exactly this.

Check my resume
Found this course useful? Share it.
ShareXLinkedIn

Comments

0

Join the conversation. Sign in to leave a comment — we'd love to hear your thoughts.