Computer scienceProgramming languagesKotlinControl flowExceptionsException types

Catching supertype

3 minutes read

Previously, we discussed the hierarchy of exceptions. This understanding will be useful when it comes to catching multiple exceptions and handling supertypes as we will see in this topic, which will prepare you to handle exceptions like a pro.

Catching multiple exceptions

Imagine a situation where you encounter different types of exceptions and want to handle them differently. The straightforward approach is to use separate catch blocks for each exception. Let's consider the following example:

fun main() {
    val input = readln()
    try {
        println(100 / input.toInt())
    } catch (e: Exception) {
        println("What else can go wrong!")
    } catch (e: NumberFormatException) {
        println("You didn't type an INT number!")
    } catch (e: ArithmeticException) {
        println("You typed 0!")
    }
}

In this example, we attempt to divide the number 100 by the user input. We have specific handling for each exception type. However, if we input a non-numeric character, the code will print:

"What else can go wrong!" // This is what we will get

Meanwhile, the output we were expecting is:

"You didn't type an INT number!" // This won't be displayed!

That happens because when dealing with multiple catch blocks, if a supertype exception is caught first, all subsequent exceptions inheriting from it will be ignored. Hence, the catch block for Exception will handle all exceptions, resulting in unexpected behavior. Therefore, regardless of the input (non-numeric or zero), only the body of the catch(e: Exception) block will be executed.

Order matters

To achieve the desired output, we need to order the catch blocks appropriately:

fun main() {
    val input = readln()
    try {
        println(100 / input.toInt())
    } catch (e: NumberFormatException) {
        println("You didn't type an INT number!")
    } catch (e: ArithmeticException) {
        println("You typed 0!")
    } catch (e: Exception) {
        println("What else can go wrong!")
    }
}

By rearranging the catch blocks and placing the more specific exceptions first, we ensure that the corresponding catch block executes correctly. Now, if we run the code and input different values, we'll get the expected outputs:

Lines starting with > mean input

> 0
You typed 0!
> 100
1
> Hello!
You didn't type an INT number!

Catching all in one

In Kotlin, the when statement can help us with writing a more concise code to catch multiple exceptions in a single catch block:

fun main() {
    val input = readln()
    try {
        println(100 / input.toInt())
    } catch (e: Exception) {
        when (e) {
            is NumberFormatException -> println("You didn't type an INT number!")
            is ArithmeticException -> println("You typed 0!")
            else -> println("What else can go wrong!")
        }
    }
}

Notice that even when using the when statement, you still have to order the catch blocks correctly so that the more specific ones are always handled first.

Catching the "Exception" type

You might be wondering why we should handle a general type if we have already handled all the specific ones. Can't we just remove the catch block for the Exception type? The answer is: such an approach can lead to problems.

It is always considered a best practice to catch the supertype Exception when handling multiple exceptions. It helps us gracefully handle unknown or unexpected exceptions. Sometimes, our code might encounter exceptions that we didn't anticipate. By catching the supertype, we can handle such exceptions without causing our program to crash or behave unpredictably.

Conclusion

In this topic, you've learned how to correctly handle multiple exceptions by ordering them from more to less specific. You've also learned about the importance of handling the supertype in any try-catch block. Now it's time to practice what you've learned.

36 learners liked this piece of theory. 4 didn't like it. What about you?
Report a typo