Question

How to Catch Multiple Exceptions in Kotlin

kotlinexception

Question

In Kotlin, how can I handle multiple exception types for the same try block?

For example, I tried syntax like this:

try {
    // code that may throw exceptions
} catch (ex: MyException1, MyException2) {
    logger.warn("", ex)
}

and also:

try {
    // code that may throw exceptions
} catch (ex: MyException1 | MyException2) {
    logger.warn("", ex)
}

Both approaches produce a compilation error such as Unresolved reference: MyException2.

What is the correct way to catch multiple exceptions in Kotlin when the handling logic is the same?

Short Answer

By the end of this page, you will understand how Kotlin handles exceptions, why Java-style multi-catch syntax does not work in Kotlin, and how to write clean alternatives using multiple catch blocks or a common superclass. You will also see practical patterns for reducing duplicated exception-handling code.

Concept

In Kotlin, a try block can be followed by one or more catch blocks. Each catch block handles one exception type.

Unlike Java, Kotlin does not support multi-catch syntax such as:

catch (e: IOException | SQLException)

or comma-separated exception types.

That means if you want to handle multiple exception types, you usually have two choices:

  1. Write separate catch blocks for each exception type.
  2. Catch a common superclass if the exceptions are related and should truly be handled the same way.

This matters because exception handling is about writing code that is both:

  • safe enough to recover from failures
  • clear enough for other developers to understand

Kotlin keeps exception handling explicit. Each catch block clearly shows which exception is being handled. If several exceptions need the same logic, you can still avoid duplication by calling a shared function from multiple catch blocks.

A very important point: Kotlin exceptions are classes, so catching multiple unrelated exception types in one typed variable declaration is not valid Kotlin syntax.

Mental Model

Think of a try block as a room where different problems might happen.

Each catch block is like a different emergency responder:

  • one responder handles fire
  • one handles flooding
  • one handles electrical issues

In Kotlin, each responder has a single specialty. You cannot write one responder label that says “fire or flood” in the catch declaration itself.

If two emergencies should be handled the same way, you have two practical options:

  • send two responders who both follow the same checklist
  • create a broader responder category that covers both cases

That is exactly how Kotlin exception handling works.

Take Quiz

Syntax and Examples

The normal Kotlin syntax is:

try {
    // risky code
} catch (e: SomeException) {
    // handle exception
} catch (e: AnotherException) {
    // handle another exception
}

Option 1: Use separate catch blocks

try {
    riskyOperation()
} catch (e: MyException1) {
    logger.warn("Operation failed", e)
} catch (e: MyException2) {
    logger.warn("Operation failed", e)
}

This is the most direct Kotlin solution.

Option 2: Extract shared handling logic

If the handling code is identical, move it into a function:

fun logWarning(ex: Exception) {
    logger.warn("Operation failed", ex)
}

try {
    riskyOperation()
} catch (e: MyException1) {
    logWarning(e)
} catch (e: MyException2) {
    logWarning(e)
}

This keeps the code readable and avoids duplication.

Option 3: Catch a common superclass

If both exceptions inherit from the same custom base exception:

open class MyBaseException(message: String) : Exception(message)
class MyException1(message: String) : MyBaseException(message)
class MyException2(message: String) : MyBaseException(message)

try {
    riskyOperation()
} catch (e: MyBaseException) {
    logger.warn("Operation failed", e)
}

This works only if catching the broader type is semantically correct.

Important note

Do not catch a very broad type like Exception unless you really want to handle all exceptions in the same way:

try {
    riskyOperation()
} catch (e: Exception) {
    logger.warn("Something failed", e)
}

This can hide problems you did not intend to handle.

Step by Step Execution

Consider this example:

class MyException1(message: String) : Exception(message)
class MyException2(message: String) : Exception(message)

fun riskyOperation(type: Int) {
    if (type == 1) throw MyException1("First problem")
    if (type == 2) throw MyException2("Second problem")
    println("Success")
}

fun main() {
    try {
        riskyOperation(2)
    } catch (e: MyException1) {
        println("Handled MyException1: ${e.message}")
    } catch (e: MyException2) {
        println("Handled MyException2: ${e.message}")
    }
}

What happens step by step

  1. main() starts.
  2. The try block calls riskyOperation(2).
  3. Inside riskyOperation, type == 2 is true.
  4. MyException2("Second problem") is thrown.
  5. Kotlin leaves the try block immediately.
  6. It checks the first catch block:
    • catch (e: MyException1) does not match.
  7. It checks the second catch block:
    • catch (e: MyException2) does match.
  8. The matching catch block runs and prints:
Handled MyException2: Second problem

Key rule

Kotlin checks catch blocks from top to bottom, and the first matching one wins.

Real World Use Cases

Handling multiple exception types is common in real applications.

File and network operations

A function might fail because:

  • a file does not exist
  • a network request times out
  • data is malformed

Example:

try {
    loadUserData()
} catch (e: FileNotFoundException) {
    println("Missing file")
} catch (e: IllegalArgumentException) {
    println("Invalid data format")
}

API clients

When calling external services, you may want similar logging for several expected failures:

  • authentication error
  • validation error
  • timeout

Database access

A query may fail because of:

  • bad input
  • connection issues
  • missing records

Separate catch blocks let you decide whether to:

  • log the issue
  • retry the operation
  • return a fallback result
  • show a user-friendly message

Data parsing

Parsing user input, JSON, or CSV often throws different exceptions. You may want to log them all but still distinguish them for debugging.

Take Quiz

Real Codebase Usage

In real Kotlin projects, developers rarely solve this by trying to force multi-catch syntax. Instead, they use patterns that keep exception handling maintainable.

Pattern 1: Multiple catches with shared helper

fun handleFailure(ex: Exception) {
    logger.warn("Request failed", ex)
}

try {
    processRequest()
} catch (e: MyException1) {
    handleFailure(e)
} catch (e: MyException2) {
    handleFailure(e)
}

This is common because it is explicit and avoids repeated code.

Pattern 2: Catch a domain-specific parent exception

open class PaymentException(message: String) : Exception(message)
class CardDeclinedException : PaymentException("Card declined")
class PaymentTimeoutException : PaymentException("Payment timeout")

try {
    chargeCard()
} catch (e: PaymentException) {
    logger.warn("Payment failed", e)
}

This works well when the exception types belong to the same domain.

Pattern 3: Guard clauses before exceptions happen

Sometimes the best exception handling is preventing exceptions with validation.

fun parseAge(input: String): Int {
    require(input.isNotBlank()) { "Age is required" }
    return input.toInt()
}

Pattern 4: Broad catch only at application boundaries

In services or background jobs, some teams catch broad exceptions only at the outermost layer:

try {
    runJob()
} catch (e: Exception) {
    logger.error("Job crashed", e)
}

Inside the codebase, they still prefer specific catches.

Pattern 5: Rethrow after logging

try {
    saveData()
} catch (e: MyException1) {
    logger.warn("Save failed", e)
    throw e
} catch (e: MyException2) {
    logger.warn("Save failed", e)
    throw e
}

This is useful when you want both logging and upstream handling.

Common Mistakes

1. Using Java multi-catch syntax in Kotlin

Broken code:

try {
    riskyOperation()
} catch (e: MyException1 | MyException2) {
    logger.warn("Failed", e)
}

Why it fails:

  • Kotlin does not support | multi-catch syntax.

Use this instead:

try {
    riskyOperation()
} catch (e: MyException1) {
    logger.warn("Failed", e)
} catch (e: MyException2) {
    logger.warn("Failed", e)
}

2. Trying comma-separated exception types

Broken code:

catch (e: MyException1, MyException2)

Why it fails:

  • A Kotlin catch parameter has one type only.

3. Catching Exception too early

Broken code:

try {
    riskyOperation()
} catch (e: Exception) {
    println("Something went wrong")
} catch (e: MyException1) {
    println("Specific handling")
}

Why it is wrong:

  • The broad Exception catch comes first.
  • The later specific catch becomes unreachable.

Correct order:

try {
    riskyOperation()
} catch (e: MyException1) {
    println("Specific handling")
} catch (e: Exception) {
    println("Fallback handling")
}

4. Catching overly broad exceptions unnecessarily

try {
    riskyOperation()
} catch (e: Exception) {
    logger.warn("Ignored", e)
}

Problem:

  • You may hide real bugs.
  • Debugging becomes harder.

Prefer specific exception types when possible.

5. Duplicating large catch logic

If several catch blocks contain lots of identical code, extract a helper function instead of copying everything repeatedly.

Comparisons

ApproachKotlin SupportBest WhenTrade-off
Separate catch blocksYesYou want explicit handling for each typeSlight repetition
Shared helper functionYesHandling logic is the sameStill needs multiple catches
Catch common superclassYesExceptions are part of the same hierarchyCan become too broad if misused
Catch ExceptionYesFinal fallback at app boundaryMay hide unexpected problems
Java-style multi-catch with ``NoNever in Kotlin

Separate catches vs common superclass

try {
    riskyOperation()
} catch (e: MyException1) {
    handleFailure(e)
} catch (e: MyException2) {
    handleFailure(e)
}

Use this when the exception types are different and you want explicit control.

try {
    riskyOperation()
} catch (e: MyBaseException) {
    handleFailure(e)
}

Use this when both exceptions genuinely belong to the same category.

Kotlin vs Java

FeatureKotlinJava
One catch for one exception typeYesYes
Multi-catch with ``No
Catch common superclassYesYes
Take Quiz

Cheat Sheet

try {
    riskyOperation()
} catch (e: MyException1) {
    // handle MyException1
} catch (e: MyException2) {
    // handle MyException2
}

Rules

  • A Kotlin catch block handles one type.
  • Kotlin does not support:
    • catch (e: A | B)
    • catch (e: A, B)
  • catch blocks are checked top to bottom.
  • Put more specific exceptions first.
  • Use a shared helper function if multiple catches do the same thing.
  • Catch a common superclass only if it makes sense in your domain.
  • Avoid catching broad Exception unless you need a real fallback.

Shared-handler pattern

fun logError(e: Exception) {
    logger.warn("Failed", e)
}

try {
    riskyOperation()
} catch (e: MyException1) {
    logError(e)
} catch (e: MyException2) {
    logError(e)
}

Common-superclass pattern

open class AppException(message: String) : Exception(message)
class MyException1(message: String) : AppException(message)
class MyException2(message: String) : AppException(message)

try {
    riskyOperation()
} catch (e: AppException) {
    logger.warn("Failed", e)
}

FAQ

Can Kotlin catch multiple exceptions in one catch block?

Not with Java-style multi-catch syntax. In Kotlin, one catch block has one declared type.

What should I do if two exceptions need the same handling?

Use two separate catch blocks and call the same helper function, or catch a shared superclass if both exceptions belong to it.

Why does catch (e: A | B) fail in Kotlin?

Because Kotlin does not support the Java multi-catch | syntax.

Can I just catch Exception instead?

Yes, but only if you intentionally want to handle all exceptions the same way. It is often too broad.

In what order should catch blocks be written?

From most specific to most general. Otherwise, broader catches can make specific ones unreachable.

Is catching a superclass always a good idea?

No. It is only a good idea when the grouped exceptions really represent the same kind of failure.

Does Kotlin have checked exceptions like Java?

No. Kotlin does not enforce checked exceptions, but runtime exception handling with try and catch still works normally.

Take Quiz

Mini Project

Description

Build a small Kotlin program that simulates loading a user profile. The operation can fail for different reasons, and you want to handle multiple exception types cleanly. This project demonstrates the Kotlin way to deal with several exceptions that share the same logging or recovery behavior.

Goal

Create a Kotlin program that throws different custom exceptions and handles them using separate catch blocks with shared logic.

Requirements

  1. Create two custom exception classes.
  2. Write a function that throws one of the exceptions based on input.
  3. Use a try block to call the function.
  4. Handle both exception types with separate catch blocks.
  5. Reuse the same helper function to log or print the error.
Take Quiz

Keep learning

Related questions

View more