Kotlin Custom Exceptions

Kotlin lets you define your own exception classes. Custom exceptions give errors a clear name that matches your domain. Instead of a generic IllegalArgumentException, your code can throw an InvalidEmailException or InsufficientFundsException — making the cause immediately obvious.

Creating a Custom Exception

class InvalidAgeException(age: Int) :
    IllegalArgumentException("Age must be 0–120, but got $age")

fun validateAge(age: Int) {
    if (age !in 0..120) throw InvalidAgeException(age)
}

fun main() {
    try {
        validateAge(150)
    } catch (e: InvalidAgeException) {
        println("Caught: ${e.message}")
    }
}

Output:

Caught: Age must be 0–120, but got 150

Exception Hierarchy


Throwable
├── Error (serious system errors — do not catch)
│   └── OutOfMemoryError, StackOverflowError
└── Exception (program errors — can be caught)
    ├── RuntimeException (unchecked)
    │   ├── IllegalArgumentException   ← extend this for bad input
    │   ├── IllegalStateException      ← extend this for wrong state
    │   └── NullPointerException
    └── IOException (checked in Java, unchecked in Kotlin)

Your custom exceptions extend whichever base fits best.

Custom Exception with Extra Fields

class InsufficientFundsException(
    val accountId: String,
    val requested: Double,
    val available: Double
) : Exception(
    "Account $accountId: requested ₹$requested but only ₹$available available"
)

fun withdraw(accountId: String, balance: Double, amount: Double): Double {
    if (amount > balance) throw InsufficientFundsException(accountId, amount, balance)
    return balance - amount
}

fun main() {
    try {
        val remaining = withdraw("ACC-101", 500.0, 800.0)
        println("New balance: ₹$remaining")
    } catch (e: InsufficientFundsException) {
        println("Error: ${e.message}")
        println("Account: ${e.accountId} | Requested: ₹${e.requested}")
    }
}

Output:

Error: Account ACC-101: requested ₹800.0 but only ₹500.0 available
Account: ACC-101 | Requested: ₹800.0

Exception Hierarchy for a Domain

// Base exception for the entire app
open class AppException(message: String, cause: Throwable? = null) :
    Exception(message, cause)

// Domain-specific exceptions
class AuthException(message: String) : AppException(message)
class DatabaseException(message: String, cause: Throwable? = null) :
    AppException(message, cause)
class ValidationException(val field: String, message: String) :
    AppException("Validation failed on '$field': $message")

fun authenticate(username: String, password: String) {
    if (username.isBlank()) throw ValidationException("username", "cannot be blank")
    if (password.length < 8) throw ValidationException("password", "minimum 8 characters")
    if (username != "admin") throw AuthException("User '$username' not found")
    println("Authenticated: $username")
}

fun main() {
    val testCases = listOf(
        Pair("", "secret123"),
        Pair("alice", "short"),
        Pair("bob", "password123"),
        Pair("admin", "password123")
    )

    testCases.forEach { (user, pass) ->
        try {
            authenticate(user, pass)
        } catch (e: ValidationException) {
            println("Validation: ${e.message}")
        } catch (e: AuthException) {
            println("Auth: ${e.message}")
        }
    }
}

Output:

Validation: Validation failed on 'username': cannot be blank
Validation: Validation failed on 'password': minimum 8 characters
Auth: User 'bob' not found
Authenticated: admin

Wrapping External Exceptions

class DataParseException(rawData: String, cause: Throwable) :
    AppException("Failed to parse: '$rawData'", cause)

fun parseUserId(raw: String): Int {
    return try {
        raw.toInt()
    } catch (e: NumberFormatException) {
        throw DataParseException(raw, e)
    }
}

fun main() {
    try {
        val id = parseUserId("abc123")
    } catch (e: DataParseException) {
        println("${e.message}")
        println("Root cause: ${e.cause?.message}")
    }
}

Output:

Failed to parse: 'abc123'
Root cause: For input string: "abc123"

Leave a Comment

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