← Back to Kotlin Course | Chapter 13: Error Handling & Testing | Lesson 2 of 6

Custom Exceptions

A custom exception is your own named kind of error, so your program can describe exactly what went wrong in your own words.
Syntax
kotlin
class CustomException(message: String) : Exception(message)

class DetailedException(val field: Type, message: String) : Exception(message)

throw CustomException("message")

Declaring a Custom Exception

A custom exception class extends Exception (or a subclass of it), typically forwarding a message to the superclass constructor so it can be printed and inspected like any built-in exception.

Example: Declaring a Custom Exception

markup
// Define a class `InsufficientFundsException`
class InsufficientFundsException(message: String) : Exception(message)

// Define the function `withdraw` taking `balance`, `amount` and returning a Double
fun withdraw(balance: Double, amount: Double): Double {
    if (amount > balance) throw InsufficientFundsException("Cannot withdraw $amount from balance $balance")
    // Return `balance - amount` from this function
    return balance - amount
}

// Entry point: execution of the program starts here
fun main() {
    try {
        withdraw(100.0, 150.0)
    } catch (e: InsufficientFundsException) {
        // Print "Error: ${e.message}" to the console, with a trailing newline
        println("Error: ${e.message}")
    }
}

Adding Extra Properties

A custom exception can carry additional properties beyond a message, giving catching code structured information to act on programmatically instead of just parsing text.

Example: Adding Extra Properties

markup
// Define a class `ValidationException`
class ValidationException(val field: String, message: String) : Exception(message)

// Define the function `validateAge` taking `age`
fun validateAge(age: Int) {
    if (age < 0) throw ValidationException("age", "Age cannot be negative")
}

// Entry point: execution of the program starts here
fun main() {
    try {
        validateAge(-5)
    } catch (e: ValidationException) {
        // Print "Invalid field ${e.field}: ${e.message}" to the console, with a trailing newline
        println("Invalid field ${e.field}: ${e.message}")
    }
}

Building a Hierarchy of Custom Exceptions

Related custom exceptions can share a common open base class, letting calling code catch either a specific exception type or the more general base type depending on how precise it needs to be.

Example: Building a Hierarchy of Custom Exceptions

markup
open class AppException(message: String) : Exception(message)
// Define a class `NetworkException`
class NetworkException(message: String) : AppException(message)
// Define a class `DatabaseException`
class DatabaseException(message: String) : AppException(message)

// Entry point: execution of the program starts here
fun main() {
    // Declare a read-only variable `errors`, set to `listOf(NetworkException("Timeout"), DatabaseException("Connection lost"))`
    val errors = listOf(NetworkException("Timeout"), DatabaseException("Connection lost"))
    // Loop over `errors`, binding each item to `error`
    for (error in errors) {
        try {
            throw error
        } catch (e: AppException) {
            // Print "App error: ${e.message}" to the console, with a trailing newline
            println("App error: ${e.message}")
        }
    }
}

Including a Cause

A custom exception's constructor can also accept and forward a cause (another Throwable), preserving the original underlying error while presenting a clearer, higher-level exception to callers.

Example: Including a Cause

markup
// Define a class `DataLoadException`
class DataLoadException(message: String, cause: Throwable) : Exception(message, cause)

// Entry point: execution of the program starts here
fun main() {
    try {
        try {
            throw NumberFormatException("bad number")
        } catch (e: NumberFormatException) {
            throw DataLoadException("Failed to load data", e)
        }
    } catch (e: DataLoadException) {
        // Print "${e.message}, caused by: ${e.cause?.message}" to the console, with a trailing newline
        println("${e.message}, caused by: ${e.cause?.message}")
    }
}
Related Topics
{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
Common Mistakes
  1. Extending Throwable directly instead of Exception (or RuntimeException), which is almost never what you actually want for application-level errors.
  2. Forgetting to pass a message (and optionally a cause) up to the superclass constructor, resulting in exceptions with no useful description when printed.
  3. Creating overly specific exception subclasses for every tiny situation instead of using a smaller number of well-designed, meaningful exception types.
Chapter Summary
  • A custom exception is typically declared as class MyException(message: String) : Exception(message).
  • Extending Exception (a checked-style base, though Kotlin doesn't enforce checked exceptions) or RuntimeException are both common choices.
  • Custom exceptions can carry extra properties beyond just a message, useful for programmatic error handling.
  • A well-designed set of custom exceptions communicates failure reasons clearly to code that catches them.
🔒

Chapter Quiz — Complete all 6 topics to unlock

0/6 topics done

Complete these topics first:

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.