Exceptions and Error Handling
|
This section documents Kotlin 2.4.x on the JVM, as published at kotlinlang.org, which is the reference these pages are written and verified against. This content was generated with the assistance of AI and should be verified against kotlinlang.org before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Kotlin’s exception model is Java’s Throwable hierarchy at the bytecode level, with two source-level
differences: try is an expression, and there are no checked exceptions at all.
try/catch/finally as an Expression
Like if and when (Control Flow), try can produce a value — the value of whichever branch (the try block or a matching catch) actually ran:
fun parseOrDefault(text: String): Int =
try {
text.toInt()
} catch (e: NumberFormatException) {
0
} finally {
println("attempted to parse \"$text\"") // finally still runs; its value is never the expression's
}
println(parseOrDefault("42")) // prints the finally message, then 42
println(parseOrDefault("oops")) // prints the finally message, then 0
Multiple catch blocks work as in Java, matched top-to-bottom against the most specific type first.
No Checked Exceptions
Kotlin has no checked-exception distinction at the language level — every exception behaves like Java’s
unchecked (RuntimeException) ones: nothing forces a caller to declare throws or wrap a call in try/catch,
even when calling Java code whose method signature does declare checked exceptions:
// Java: void readFile(String path) throws IOException { ... }
fun loadConfig(path: String) {
readFile(path) // no throws declaration and no try/catch required, even though the Java method
} // declares a checked IOException -- Kotlin simply doesn't enforce it
This was a deliberate design choice: JetBrains observed that Java’s checked exceptions, in practice, tend to be either genuinely handled (which still works fine in Kotlin) or immediately wrapped/rethrown/swallowed as boilerplate — so Kotlin drops the compiler enforcement and leaves the decision of what to catch entirely to the code’s own logic.
Custom Exceptions
Custom exceptions extend Exception (or a more specific existing type) exactly as in Java:
class InsufficientFundsException(
message: String,
val shortfall: Double
) : Exception(message)
fun withdraw(balance: Double, amount: Double): Double {
if (amount > balance) {
throw InsufficientFundsException(
"cannot withdraw $amount from balance $balance",
shortfall = amount - balance
)
}
return balance - amount
}
The Result Type and runCatching
kotlin.Result<T> models a computation that either succeeded (holding a value) or failed (holding a
Throwable), as an alternative to throw/catch for error paths that are an ordinary, expected part of a
function’s contract rather than an exceptional condition. runCatching { } builds one from a block that may
throw:
fun fetchUser(id: Int): Result<User> = runCatching {
httpClient.get("/users/$id").parseAsUser() // if this throws, it's captured as Result.failure
}
val result = fetchUser(42)
result
.onSuccess { user -> println("got ${user.name}") }
.onFailure { error -> println("failed: ${error.message}") }
val userOrNull: User? = result.getOrNull() // null on failure, instead of propagating the exception
val userOrDefault = result.getOrDefault(User.GUEST) // a fallback value on failure
Result is especially useful at API boundaries where a caller should be able to inspect success/failure without
setting up a try/catch for every call — it composes with the rest of the functional collection/Sequence
API (Collections and Sequences) more naturally than an
exception can, since it is an ordinary value rather than a special control-flow event.
See Also
-
Control Flow —
if/when/tryall sharing the "control structure as expression" idea. -
Coroutine Context, Cancellation and Exceptions — how exceptions propagate differently inside a coroutine.
-
Testing — asserting on thrown exceptions in a test.
References
-
Kotlin docs — FAQ: Why doesn’t Kotlin have checked exceptions? (see also the general exceptions page above).