0Pricing
Kotlin Academy · Lesson

Creating Custom Exception Classes

Define domain-specific exceptions with meaningful messages and properties.

Creating Custom Exception Classes is a free Kotlin Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Kotlin Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Why Custom Exceptions?

Custom exceptions encode domain meaning: callers can catch them specifically, get useful messages, and act differently based on type.

Simplest Custom Exception

Subclass Exception (or a specific subclass like RuntimeException) and pass a message to the super constructor.

class InvalidEmailException(message: String) : Exception(message)
fun main() {
    try {
        throw InvalidEmailException("missing @")
    } catch (e: InvalidEmailException) {
        println("caught: ${e.message}")
    }
}

Exception with Properties

Add fields to convey context: HTTP status, field name, retry count, etc.

class HttpError(
    message: String,
    val statusCode: Int
) : Exception(message)
fun main() {
    try {
        throw HttpError("Not Found", 404)
    } catch (e: HttpError) {
        println("HTTP ${e.statusCode}: ${e.message}")
    }
}

Exception with Cause

Pass a cause to preserve the original exception in the chain.

class DatabaseError(message: String, cause: Throwable) : Exception(message, cause)
fun main() {
    try {
        try { error("connection refused") }
        catch (e: Throwable) { throw DatabaseError("query failed", e) }
    } catch (e: DatabaseError) {
        println("${e.message} (caused by: ${e.cause?.message})")
    }
}

Checked vs Unchecked

Kotlin treats all exceptions as unchecked (no throws declaration required). Subclass RuntimeException by convention.

class ValidationException(field: String, message: String) :
    RuntimeException("[$field] $message")
fun main() {
    try {
        throw ValidationException("age", "must be positive")
    } catch (e: ValidationException) {
        println(e.message)
    }
}

Hierarchy of Domain Exceptions

Build a hierarchy so callers can catch a base type for general handling or specific subtypes for special cases.

open class AppException(message: String) : RuntimeException(message)
class NotFoundException(what: String) : AppException("$what not found")
class UnauthorizedException : AppException("not authorized")
fun main() {
    val errors = listOf(NotFoundException("user"), UnauthorizedException())
    for (e in errors) {
        try { throw e }
        catch (e: AppException) { println("App error: ${e.message}") }
    }
}

Sealed Exception Hierarchy

For closed sets of error types, use a sealed hierarchy — callers can exhaustively handle every variant.

sealed class ApiError(message: String) : RuntimeException(message) {
    class Timeout : ApiError("timeout")
    class NotFound(val id: String) : ApiError("not found: $id")
    class ServerError(val status: Int) : ApiError("server returned $status")
}
fun describe(e: ApiError) = when (e) {
    is ApiError.Timeout -> "request timed out"
    is ApiError.NotFound -> "id ${e.id} missing"
    is ApiError.ServerError -> "5xx: ${e.status}"
}
fun main() {
    println(describe(ApiError.NotFound("user-42")))
}

Naming Convention

End custom exception class names with Exception or Error for clarity. Use the past tense or noun (e.g. NotFound, InvalidInput).

class InvalidInputException(msg: String) : RuntimeException(msg)
class UserNotFoundException(id: Int) : RuntimeException("user $id not found")
fun main() {
    try { throw UserNotFoundException(7) }
    catch (e: UserNotFoundException) { println(e.message) }
}

Documenting Exceptions

Use KDoc @throws to document which exceptions a function may raise.

/**
 * @throws InvalidEmailException if the email is malformed
 */
fun validate(email: String) {
    if ("@" !in email) throw InvalidEmailException("missing @")
}
class InvalidEmailException(m: String) : RuntimeException(m)
fun main() {
    try { validate("nope") }
    catch (e: InvalidEmailException) { println(e.message) }
}

Avoid Overusing Exceptions

Exceptions are for exceptional situations. For expected outcomes (empty input, missing optional field), prefer nullable returns, sealed result types, or default values.

fun findUser(id: Int): String? = if (id == 1) "Alice" else null
fun main() {
    val name = findUser(42) ?: "(unknown)"
    println(name) // (unknown), no exception
}

Practical Example

A realistic custom exception for a fictional API client, with status and body.

class ApiException(
    message: String,
    val statusCode: Int,
    val responseBody: String
) : RuntimeException(message)
fun main() {
    try {
        throw ApiException("Bad Request", 400, "{\"error\":\"invalid\"}")
    } catch (e: ApiException) {
        println("[${e.statusCode}] ${e.message}: ${e.responseBody}")
    }
}

Quick Check

What parent class is most commonly used when defining a domain-specific Kotlin exception?

Recap

Custom exceptions encode domain meaning. Subclass RuntimeException or Exception; add properties for context; preserve causes; build hierarchies for grouped catches. Use sealed exceptions for closed sets, and prefer nullable/result types when errors are expected.

Frequently asked questions

Is the “Creating Custom Exception Classes” lesson free?

Yes — the full text of “Creating Custom Exception Classes” is free to read here on the web, and the Kotlin Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Kotlin Academy course, upgrade to CoddyKit PRO.

What will I learn in “Creating Custom Exception Classes”?

Define domain-specific exceptions with meaningful messages and properties. You practise Kotlin Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Kotlin Academy?

No prior experience is required. Kotlin Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Creating Custom Exception Classes” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Kotlin Academy lesson?

Yes. Every Kotlin Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. try/catch/finally as an Expression
  2. Creating Custom Exception Classes
  3. runCatching and Result
  4. Re-throwing and Exception Chaining
← Back to Kotlin Academy