Sobes.tech
Junior

How to properly handle exceptions in Kotlin?

sobes.tech AI

Answer from AI

In Kotlin, exception handling is done using the try...catch...finally construct. You can call functions annotated with @Throws to interact with Java code.

fun readFile(path: String): String {
    return try {
        // Attempt to execute risky code
        java.io.File(path).readText()
    } catch (e: java.io.IOException) {
        // Handle specific exception
        println("Error reading file: ${e.message}")
        "" // Return default value in case of error
    } finally {
        // Block that always executes, regardless of exception
        println("File read attempt completed")
    }
}

You can use multiple catch blocks to handle different exception types:

fun processData(data: String) {
    try {
        // Some code that may throw NumberFormatException or NullPointerException
        val number = data.toInt()
        println("Number: $number")
    } catch (e: NumberFormatException) {
        println("Invalid number format: ${e.message}")
    } catch (e: NullPointerException) {
        println("Received null data: ${e.message}")
    }
}

Kotlin does not have checked exceptions like Java. Functions can declare that they might throw exceptions using @Throws, but this is optional in Kotlin code.

For cases where a function can succeed or fail, sealed classes or the Result type (in experimental Kotlin versions) are often used instead of exceptions, especially in asynchronous code.

Example with a sealed class:

sealed class Result<out T>
data class Success<out T>(val value: T) : Result<T>()
data class Error(val exception: Throwable) : Result<Nothing>()

fun safelyDivide(a: Int, b: Int): Result<Int> {
    return try {
        if (b == 0) {
            throw ArithmeticException("Division by zero")
        }
        Success(a / b)
    } catch (e: Throwable) {
        Error(e)
    }
}

// Usage:
fun processDivision() {
    when (val divisionResult = safelyDivide(10, 0)) {
        is Success -> println("Result: ${divisionResult.value}")
        is Error -> println("Error: ${divisionResult.exception.message}")
    }
}

It's important to catch only the exceptions you can handle. Avoid empty catch blocks. If needed, log the exception information.

You can also throw exceptions explicitly using the throw keyword:

fun requirePositive(number: Int) {
    if (number <= 0) {
        throw IllegalArgumentException("Number must be positive")
    }
}