Kotlin Exception Handling
An exception is an error that occurs at runtime. Instead of crashing the entire program, Kotlin lets you catch exceptions, respond to them, and continue execution. You surround risky code with a try block and handle problems in a catch block.
Basic try-catch
fun main() {
try {
val result = 10 / 0 // ArithmeticException!
println(result)
} catch (e: ArithmeticException) {
println("Cannot divide by zero: ${e.message}")
}
println("Program continues normally")
}Output:
Cannot divide by zero: / by zero
Program continues normallytry-catch-finally
fun readData(input: String): Int {
try {
return input.trim().toInt()
} catch (e: NumberFormatException) {
println("Not a valid number: '$input'")
return -1
} finally {
println("readData() finished — always runs")
}
}
fun main() {
println(readData("42"))
println(readData("abc"))
}Output:
readData() finished — always runs
42
Not a valid number: 'abc'
readData() finished — always runs
-1Multiple catch Blocks
fun process(input: String, index: Int) {
try {
val number = input.toInt()
val items = listOf(10, 20, 30)
println("Value at $index: ${items[index] / number}")
} catch (e: NumberFormatException) {
println("Input is not a number: $input")
} catch (e: IndexOutOfBoundsException) {
println("Index $index is out of range")
} catch (e: ArithmeticException) {
println("Division by zero!")
}
}
fun main() {
process("2", 1) // 20/2 = 10
process("abc", 1) // not a number
process("2", 10) // out of range
process("0", 1) // divide by zero
}try as an Expression
val value: Int = try {
"123".toInt()
} catch (e: NumberFormatException) {
-1
}
println(value) // 123
val bad: Int = try {
"abc".toInt()
} catch (e: NumberFormatException) {
-1
}
println(bad) // -1Common Kotlin Exceptions
Exception │ Cause
────────────────────────────┼────────────────────────────────────
ArithmeticException │ divide by zero
NullPointerException │ using !! on null value
NumberFormatException │ "abc".toInt()
IndexOutOfBoundsException │ list[100] when list has 5 items
IllegalArgumentException │ require() fails
IllegalStateException │ check() fails
StackOverflowError │ infinite recursion
ClassCastException │ wrong cast (value as String when it is Int)
require and check
fun createUser(name: String, age: Int) {
require(name.isNotBlank()) { "Name cannot be empty" }
require(age in 0..120) { "Age must be 0–120, got $age" }
var status = "pending"
check(status == "pending") { "User already active" }
println("User $name created, age $age")
}
fun main() {
createUser("Alice", 30) // OK
createUser("", 25) // throws IllegalArgumentException: Name cannot be empty
}Handling Exceptions Gracefully with runCatching
val result = runCatching {
"42".toInt()
}
println(result.isSuccess) // true
println(result.getOrNull()) // 42
println(result.getOrDefault(-1)) // 42
val bad = runCatching { "abc".toInt() }
println(bad.isFailure) // true
println(bad.exceptionOrNull()?.message) // For input string: "abc"
println(bad.getOrDefault(-1)) // -1Practical Example: Safe File Parser
fun parseConfig(lines: List): Map {
val config = mutableMapOf()
lines.forEachIndexed { lineNum, line ->
try {
val parts = line.split("=")
require(parts.size == 2) { "Expected key=value format" }
config[parts[0].trim()] = parts[1].trim()
} catch (e: IllegalArgumentException) {
println("Line ${lineNum + 1} skipped: ${e.message}")
}
}
return config
}
fun main() {
val input = listOf(
"host = localhost",
"port = 8080",
"invalid line without equals",
"debug = true"
)
val config = parseConfig(input)
println(config)
} Output:
Line 3 skipped: Expected key=value format
{host=localhost, port=8080, debug=true}