# Exception Handling

Failure is always a possibility.

Kotlin finds basic errors when it analyzes your program. Errors that cannot be detected at compile time must be dealt with at runtime. In [Exceptions](javascript:void(0)), you learned to throw exceptions. In this atom, we catch exceptions.

Historically, failures were often disastrous. For example, programs written in the C language would simply stop working, lose their data, and potentially crash the operating system.

Improved error handling is a powerful way to increase code reliability. Error handling is especially important when creating reusable program components. To create a robust system, each component must be robust. With consistent error handling, components can reliably communicate problems to client code.

Modern applications often use concurrency, and a concurrent program must survive non-critical exceptions. A server, for example, should recover when an open session is terminated via an exception.

Exceptions conflate three activities:

  1. Error reporting
  2. Recovery
  3. Resource cleanup

Let’s consider each one.

# Reporting

Standard library exceptions are often adequate. For more specific exception handling, you can inherit new exception types from Exception or a subtype:

// ExceptionHandling/DefiningExceptions.kt
package exceptionhandling
import atomictest.*

class Exception1(
  val value: Int
): Exception("wrong value: $value")

open class Exception2(
  description: String
): Exception(description)

class Exception3(
  description: String
): Exception2(description)

fun main() {
  capture {
    throw Exception1(13)
  } eq "Exception1: wrong value: 13"
  capture {
    throw Exception3("error")
  } eq "Exception3: error"
}

A throw expression, as in main(), requires an instance of a Throwable subtype. To define new exception types, inherit Exception (which extends Throwable). Both Exception1 and Exception2 inherit Exception, while Exception3 inherits Exception2.

# Recovery

The ambition of exception handling is recovery. This means that you fix the problem, return the program to a stable state, and resume execution. Recovery often includes [logging](javascript:void(0)) information about the error.

Quite often, recovery isn’t possible. An exception might represent an unrecoverable program failure, either a coding error or something uncontrollable in the environment.

When an exception is thrown, the exception-handling mechanism looks for an appropriate place to continue execution. An exception keeps moving out to higher levels, from function1() that threw the exception, to function2() that calls function1(), to function3() that calls function2(), and so on until reaching main(). A matching handler catches the exception. This stops the search and runs that handler. If the program never finds a matching handler, it terminates with a console stack trace.

// ExceptionHandling/Stacktrace.kt
package stacktrace
import exceptionhandling.Exception1

fun function1(): Int =
  throw Exception1(-52)

fun function2() = function1()

fun function3() = function2()

fun main() {
//  function3()
}

Uncommenting the call to function3() produces the following stack trace:

Exception in thread "main" exceptionhandling.Exception1: wrong value: -\
52
  at stacktrace.StacktraceKt.function1(Stacktrace.kt:6)
  at stacktrace.StacktraceKt.function2(Stacktrace.kt:8)
  at stacktrace.StacktraceKt.function3(Stacktrace.kt:10)
  at stacktrace.StacktraceKt.main(Stacktrace.kt:13)
  at stacktrace.StacktraceKt.main(Stacktrace.kt)

Any of function1(), function2() or function3() can catch the exception and handle it, preventing the exception from terminating the program.

An exception handler is the catch keyword followed by a parameter list containing the exception you’re handling. This is followed by a block of code implementing the recovery.

In the following example, the function toss() produces different exceptions for arguments 1-3, otherwise it returns “OK”. test() contains a complete set of handlers for the toss() function:

// ExceptionHandling/Handlers.kt
package exceptionhandling
import atomictest.eq

fun toss(which: Int) = when (which) {
  1 -> throw Exception1(1)
  2 -> throw Exception2("Exception 2")
  3 -> throw Exception3("Exception 3")
  else -> "OK"
}

fun test(which: Int): Any? =
  try {
    toss(which)
  } catch (e: Exception1) {
    e.value
  } catch (e: Exception3) {
    e.message
  } catch (e: Exception2) {
    e.message
  }

fun main() {
  test(0) eq "OK"
  test(1) eq 1
  test(2) eq "Exception 2"
  test(3) eq "Exception 3"
}

When you call toss() you must catch all relevant toss() exceptions, allowing non-relevant exceptions to “bubble up” and be caught elsewhere.

The entire try-catch in test() is a single expression: it returns either the last expression of the try body or the last expression of the catch clause matching an exception. If no catch handles the exception, that exception is thrown further up the stack. If uncaught, it generates a stack trace.

Because Exception3 extends Exception2, an Exception3 is handled as an Exception2 if Exception2’s catch appears in the sequence of handlers before Exception3’s catch:

// ExceptionHandling/Hierarchy.kt
package exceptionhandling
import atomictest.eq

fun testCatchOrder(which: Int) =
  try {
    toss(which)
  } catch (e: Exception2) {    // [1]
    "Handler for Exception2 got ${e.message}"
  } catch (e: Exception3) {    // [2]
    "Handler for Exception3 got ${e.message}"
  }

fun main() {
  testCatchOrder(2) eq
    "Handler for Exception2 got Exception 2"
  testCatchOrder(3) eq
    "Handler for Exception2 got Exception 3"
}

The catch-clause order means an Exception3 is caught by line [1], despite the more specific type of exception handler in line [2].

# Exception Subtypes

In testCode(), an incorrect code argument throws an IllegalArgumentException:

// ExceptionHandling/LibraryException.kt
package exceptionhandling
import atomictest.*

fun testCode(code: Int) {
  if (code <= 1000) {
    throw IllegalArgumentException(
      "'code' must be > 1000: $code")
  }
}

fun main() {
  try {
    // A1 is 161 in base-16 (hex) notation:
    testCode("A1".toInt(16))
  } catch (e: IllegalArgumentException) {
    e.message eq
      "'code' must be > 1000: 161"
  }
  try {
    testCode("0".toInt(1))
  } catch (e: IllegalArgumentException) {
    e.message eq
      "radix 1 was not in valid range 2..36"
  }
}

An IllegalArgumentException is thrown by both testCode() and the library function toInt(radix). This results in the somewhat confusing error messages in main(). The problem is that we are using the same exception to represent two different issues. We solve it by throwing a new exception type called IncorrectInputException for our error:

// ExceptionHandling/NewException.kt
package exceptionhandling
import atomictest.eq

class IncorrectInputException(
  message: String
): Exception(message)

fun checkCode(code: Int) {
  if (code <= 1000) {
    throw IncorrectInputException(
      "Code must be > 1000: $code")
  }
}

fun main() {
  try {
    checkCode("A1".toInt(16))
  } catch (e: IncorrectInputException) {
    e.message eq "Code must be > 1000: 161"
  } catch (e: IllegalArgumentException) {
    "Produces error" eq "if it gets here"
  }
  try {
    checkCode("1".toInt(1))
  } catch (e: IncorrectInputException) {
    "Produces error" eq "if it gets here"
  } catch (e: IllegalArgumentException) {
    e.message eq
      "radix 1 was not in valid range 2..36"
  }
}

Now each issue has its own handler.

Resist creating too many exception types. As a rule of thumb, use different exception types to distinguish different handling schemes, and use different constructor parameters to provide details for a particular handling scheme.

# Resource Cleanup

When failure is inevitable, automatic resource cleanup helps other parts of the program to continue running safely.

finally ensures resource cleanup during exception handling. A finally clause always runs, regardless of whether you leave a try block normally or exceptionally:

// ExceptionHandling/TryFinally.kt
package exceptionhandling
import atomictest.*

fun checkValue(value: Int) {
  try {
    trace(value)
    if (value <= 0)
      throw IllegalArgumentException(
        "value must be positive: $value")
  } finally {
    trace("In finally clause for $value")
  }
}

fun main() {
  listOf(10, -10).forEach {
    try {
      checkValue(it)
    } catch (e: IllegalArgumentException) {
      trace("In catch clause for main()")
      trace(e.message)
    }
  }
  trace eq """
    10
    In finally clause for 10
    -10
    In finally clause for -10
    In catch clause for main()
    value must be positive: -10
  """
}

finally works even with intermediate catch clauses. For example, suppose a switch must be turned off when you’re done with it:

// ExceptionHandling/GuaranteedCleanup.kt
package exceptionhandling
import atomictest.eq

data class Switch(
  var on: Boolean = false,
  var result: String = "OK"
)

fun testFinally(i: Int): Switch {
  val sw = Switch()
  try {
    sw.on = true
    when (i) {
      0 -> throw IllegalStateException()
      1 -> return sw                 // [1]
    }
  } catch (e: IllegalStateException) {
    sw.result = "exception"
  } finally {
    sw.on = false
  }
  return sw
}

fun main() {
  testFinally(0) eq
    "Switch(on=false, result=exception)"
  testFinally(1) eq
    "Switch(on=false, result=OK)"    // [2]
  testFinally(2) eq
    "Switch(on=false, result=OK)"
}

Even if we return inside a try ([1]), the finally clause still runs ([2]). Whether testFinally() completes normally or with an exception, the finally clause always executes.

# Exception Handling in AtomicTest

This book uses AtomicTest’s capture() to ensure that expected exceptions are thrown. capture() takes a function argument and returns a CapturedException object containing the exception class and error message:

// ExceptionHandling/CaptureImplementation.kt
package exceptionhandling
import atomictest.CapturedException

fun capture(f:() -> Unit): CapturedException =
  try {                                 // [1]
    f()
    CapturedException(null,
      "<Error>: Expected an exception") // [2]
  } catch (e: Throwable) {              // [3]
    CapturedException(e::class,         // [4]
      if (e.message != null) ": ${e.message}"
      else "")
  }

fun main() {
  capture {
    throw Exception("!!!")
  } eq "Exception: !!!"                 // [5]
  capture {
    1
  } eq "<Error>: Expected an exception"
}

capture() calls its function argument f within a try block ([1]), handling all possible exceptions by catching Throwable ([3]). If no exception is thrown, the CapturedException message indicates that an exception was expected ([2]). If an exception is caught, the returned CapturedException contains the exception class and a message ([4]). A CapturedException can be compared to a String using eq ([5]).

Ordinarily you won’t catch Throwable, but will process each specific exception type.

# Guidelines

Recovering from exceptions turns out to be remarkably rare, considering that recovery was the original intent. The primary purpose of exceptions in Kotlin is to discover program bugs, not recovery. Catching exceptions in ordinary Kotlin code is thus a “code smell.”

Here are guidelines for programming with exceptions in Kotlin:

  1. Logic Errors: These are bugs in your code. Either don’t catch them at all (and produce a stack trace), or catch them at the top level of your application and report the bugs, possibly restarting the affected operation.

  2. Data Errors

    : These are errors from bad data that the programmer cannot control. The application must somehow deal with the problem without blaming it on program logic. For example, we’ve used

    String.toInt()
    

    this atom, which throws an exception for an inappropriate

    String
    

    . It also has a companion

    String.toIntOrNull()
    

    that produces a

    null
    

    upon failure so you can use it in an expression such as

    val n = string.toIntOrNull() ?: default
    

    .

    The Kotlin library is designed around dealing with a bad result by returning a null instead of throwing an exception. Operations that are expected to occasionally fail will usually have an “OrNull” version that you can use instead of the exception version.

  3. [Check instructions](javascript:void(0)) test for logic errors. These produce exceptions when they find a bug, but they look like function calls so you don’t explicitly throw exceptions in your code.

  4. Input/Output Errors: These are external conditions that you can’t control and you can’t ignore. However, using the “OrNull” approach rapidly obscures the understandability of the code. More importantly, you often can recover from I/O errors, typically by retrying the operation. Thus, I/O operations in Kotlin throw exceptions, so you’ll have code in your applications that handle those and attempt to recover from them.

Exercises and solutions can be found at www.AtomicKotlin.com.