# Exceptions

The word “exception” is used in the same sense as the phrase “I take exception to that.”

An exceptional condition prevents the continuation of the current function or scope. At the point the problem occurs, you might not know what to do with it, but you cannot continue within the current context. You don’t have enough information to fix the problem. So you must stop and hand the problem to another context that’s able to take appropriate action.

This atom covers the basics of exceptions as an error-reporting mechanism. In [Section VI: Preventing Failure](javascript:void(0)), we look at other ways to deal with problems.

It’s important to distinguish an exceptional condition from a normal problem. A normal problem has enough information in the current context to cope with the issue. With an exceptional condition, you cannot continue processing. All you can do is leave, relegating the problem to an external context. This is what happens when you throw an exception. The exception is the object that is “thrown” from the site of the error.

Consider toInt(), which converts a String to an Int. What happens if you call this function for a String that doesn’t contain an integer value?

// Exceptions/ToIntException.kt
package exceptions

fun erroneousCode() {
  // Uncomment this line to get an exception:
  // val i = "1$".toInt()        // [1]
}

fun main() {
  erroneousCode()
}

Uncommenting line [1] produces an exception. Here, the failing line is commented so we don’t stop the book’s build, which checks whether each example compiles and runs as expected.

When an exception is thrown, the path of execution—the one that can’t be continued—stops, and the exception object ejects from the current context. Here, it exits the context of erroneousCode() and goes out to the context of main(). In this case, Kotlin only reports the error; the programmer has presumably made a mistake and must fix the code.

When an exception isn’t caught, the program aborts and displays a stack trace containing detailed information. Uncommenting line [1] in ToIntException.kt, produces the following output:

Exception in thread "main" java.lang.NumberFormatException: For input s\
tring: "1$"
  at java.lang.NumberFormatException.forInputString(NumberFormatExcepti\
on.java:65)
  at java.lang.Integer.parseInt(Integer.java:580)
  at java.lang.Integer.parseInt(Integer.java:615)
  at ToIntExceptionKt.erroneousCode(at ToIntException.kt:6)
  at ToIntExceptionKt.main(at ToIntException.kt:10)

The stack trace gives details such as the file and line where the exception occurred, so you can quickly discover the issue. The last two lines show the problem: in line 10 of main() we call erroneousCode(). Then, more precisely, in line 6 of erroneousCode() we call toInt().

To avoid commenting and uncommenting code to display exceptions, we use the capture() function from the AtomicTest package:

// Exceptions/IntroducingCapture.kt
import atomictest.*

fun main() {
  capture {
    "1$".toInt()
  } eq "NumberFormatException: " +
    """For input string: "1$""""
}

Using capture(), we compare the generated exception to the expected error message. capture() isn’t very helpful for normal programming—it’s designed specifically for this book, so you can see the exception and know that the output has been checked by the book’s build system.

Another strategy when you can’t successfully produce the expected result is to return null, which is a special constant denoting “no value.” You can return null instead of a value of any type. Later in [Nullable Types](javascript:void(0)) we discuss the way null affects the type of the resulting expression.

The Kotlin standard library contains String.toIntOrNull() which performs the conversion if the String contains an integer number, or produces null if the conversion is impossible—null is a simple way to indicate failure:

// Exceptions/IntroducingNull.kt
import atomictest.eq

fun main() {
  "1$".toIntOrNull() eq null
}

Suppose we calculate average income over a period of months:

// Exceptions/AverageIncome.kt
package firstversion
import atomictest.*

fun averageIncome(income: Int, months: Int) =
  income / months

fun main() {
  averageIncome(3300, 3) eq 1100
  capture {
    averageIncome(5000, 0)
  } eq "ArithmeticException: / by zero"
}

If months is zero, the division in averageIncome() throws an ArithmeticException. Unfortunately, this doesn’t tell us anything about why the error occurred, what the denominator means and whether it can legally be zero in the first place. This is clearly a bug in the code—averageIncome() should cope with a months of 0 in a way that prevents a divide-by-zero error.

Let’s modify averageIncome() to produce more information about the source of the problem. If months is zero, we can’t return a regular integer value as a result. One strategy is to return null:

// Exceptions/AverageIncomeWithNull.kt
package withnull
import atomictest.eq

fun averageIncome(income: Int, months: Int) =
  if (months == 0)
    null
  else
    income / months

fun main() {
  averageIncome(3300, 3) eq 1100
  averageIncome(5000, 0) eq null
}

If a function can return null, Kotlin requires that you check the result before using it (this is covered in [Nullable Types](javascript:void(0))). Even if you only want to display output to the user, it’s better to say “No full month periods have passed,” rather than “Your average income for the period is: null.”

Instead of executing averageIncome() with the wrong arguments, you can throw an exception—escape and force some other part of the program to manage the issue. You could just allow the default ArithmeticException, but it’s often more useful to throw a specific exception with a detailed error message. When, after a couple of years in production, your application suddenly throws an exception because a new feature calls averageIncome() without properly checking the arguments, you’ll be grateful for that message:

// Exceptions/AverageIncomeWithException.kt
package properexception
import atomictest.*

fun averageIncome(income: Int, months: Int) =
  if (months == 0)
    throw IllegalArgumentException(    // [1]
      "Months can't be zero")
  else
    income / months

fun main() {
  averageIncome(3300, 3) eq 1100
  capture {
    averageIncome(5000, 0)
  } eq "IllegalArgumentException: " +
    "Months can't be zero"
}
  • [1] When throwing an exception, the throw keyword is followed by the exception to be thrown, along with any arguments it might need. Here we use the standard exception class IllegalArgumentException.

Your goal is to generate the most useful messages possible to simplify the support of your application in the future. Later you’ll learn to define your own exception types and make them specific to your circumstances.

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