Kotlin Exception Handling

An exception is an event that disrupts the normal flow of a program — like trying to divide by zero, reading a file that does not exist, or parsing text that is not a valid number. Exception handling lets your program detect these problems, respond gracefully, and continue running rather than crashing.

try / catch / finally

Wrap risky code in a try block. If an exception occurs, execution jumps to the matching catch block. The finally block always runs, regardless of whether an exception happened.

try {
    val result = 10 / 0
    println(result)
} catch (e: ArithmeticException) {
    println("Cannot divide by zero: ${e.message}")
} finally {
    println("This always runs")
}

// Output:
// Cannot divide by zero: / by zero
// This always runs

Diagram — try/catch/finally Flow

try {
   risky code ──────► No exception ─────────────────────────┐
                 │                                          │
                 ▼ Exception thrown                         │
catch (e) {                                                 │
   handle error ──────────────────────────────────────────► │
}                                                           │
                                                            ▼
finally {                                            always runs here
   cleanup
}

Multiple catch Blocks

fun parseInput(input: String) {
    try {
        val number = input.toInt()
        println("Parsed: ${100 / number}")
    } catch (e: NumberFormatException) {
        println("Not a number: $input")
    } catch (e: ArithmeticException) {
        println("Cannot divide by zero")
    } catch (e: Exception) {
        println("Unexpected error: ${e.message}")
    }
}

parseInput("abc")    // Not a number: abc
parseInput("0")      // Cannot divide by zero
parseInput("5")      // Parsed: 20

try as an Expression

In Kotlin, try is an expression — it returns a value. The last expression in try or catch becomes the result.

val value: Int = try {
    "42".toInt()
} catch (e: NumberFormatException) {
    0
}
println(value)   // 42

val broken: Int = try {
    "abc".toInt()
} catch (e: NumberFormatException) {
    -1
}
println(broken)   // -1

Throwing Exceptions

Use the throw keyword to signal that something went wrong. You can throw any class that extends Throwable.

fun setAge(age: Int) {
    if (age < 0 || age > 150) {
        throw IllegalArgumentException("Age must be between 0 and 150, got $age")
    }
    println("Age set to $age")
}

try {
    setAge(-5)
} catch (e: IllegalArgumentException) {
    println("Error: ${e.message}")
}
// Error: Age must be between 0 and 150, got -5

Common Built-In Exception Types

Exception Type              | When it occurs
----------------------------|-----------------------------------------------
NullPointerException        | Accessing a member on a null reference
ArrayIndexOutOfBounds       | Accessing index beyond array size
NumberFormatException       | Parsing "abc" as a number
IllegalArgumentException    | A parameter value is not acceptable
IllegalStateException       | Object is in wrong state for the operation
ArithmeticException         | Math error (divide by zero)
ClassCastException          | Casting to an incompatible type
UnsupportedOperationException| Operation not supported by the implementation

Creating Custom Exceptions

class InsufficientFundsException(amount: Double)
    : Exception("Not enough balance. Needed: ₹$amount")

class BankAccount(var balance: Double) {
    fun withdraw(amount: Double) {
        if (amount > balance) throw InsufficientFundsException(amount)
        balance -= amount
        println("Withdrew ₹$amount. Balance: ₹$balance")
    }
}

val account = BankAccount(500.0)
try {
    account.withdraw(300.0)   // Withdrew ₹300.0. Balance: ₹200.0
    account.withdraw(400.0)   // throws
} catch (e: InsufficientFundsException) {
    println(e.message)        // Not enough balance. Needed: ₹400.0
}

require / check / error — Preconditions

Kotlin provides three functions for common validation patterns that throw exceptions automatically.

// require — validates function arguments
fun setPercentage(value: Int) {
    require(value in 0..100) { "Percentage must be 0–100, got $value" }
    println("Set to $value%")
}
setPercentage(110)   // IllegalArgumentException: Percentage must be 0–100...

// check — validates object state
var isInitialized = false
fun start() {
    check(isInitialized) { "Must call initialize() first" }
    println("Starting...")
}
start()   // IllegalStateException: Must call initialize() first

// error — unconditionally throws IllegalStateException
fun getUser(role: String): String {
    return when (role) {
        "admin" -> "Admin User"
        "guest" -> "Guest User"
        else    -> error("Unknown role: $role")
    }
}
getUser("hacker")   // IllegalStateException: Unknown role: hacker

runCatching — Functional Exception Handling

runCatching wraps a block in a Result object. Use it when you want to handle success and failure as values rather than with try/catch blocks.

val result = runCatching {
    "not a number".toInt()
}

result
    .onSuccess { println("Parsed: $it") }
    .onFailure { println("Failed: ${it.message}") }
// Failed: For input string: "not a number"

val value = runCatching { "42".toInt() }.getOrDefault(0)
println(value)   // 42

Leave a Comment

Your email address will not be published. Required fields are marked *