Catching Throwable in Scala

Because of Scala‘s pattern matching syntax it is really convenient to catch Throwable instead of Exception or any other exception type. Instead of writing:

try {
  iMayHaveIllegalState.doSomething()
} catch {
  case e: IllegalStateException => e.printStackTrace()
}

we can simply write:

try {
  iMayHaveIllegalState.doSomething()
} catch {
  case e => e.printStackTrace()
}

You might be tempted to use the above code in your programs since it is shorter but if catching Throwable in Java is evil then catching Throwable in Scala is ten times more evil.

To illustrate this we will use a small example. Lets say that we want to write a method that takes a sequence of strings and tries to find if it contains a string that represents an even number. Furthermore if any of the strings cannot get converted into an integer then we assume that the input is invalid and return false.

A possible implementation is the following:

def containsEven(nums: String*): Boolean = {
  try {
    for (i <- nums) {
      if (i.toInt % 2 == 0)
        return true
    }
  } catch { case e => () }
  false
}

From the above implementation we would expect that containsEven("1", "3") should return false and that containsEven("2", "3") should return true. Unfortunately this is not the case and regardless of the input our method always returns false. This is because in the catch block we used a pattern that catches Throwable with the expression case e => ... instead of the longer but more correct pattern case e: NumberFormatException => ... that only catches NumberFormatException. To understand why this is the cause of the bug we need to understand how Scala implements non-local return from closures.

The for loop in the above code is actually a method call to the the foreach method of the nums object (that is an instance of scala.Seq) passing the body of the for loop as a closure in the foreach method. The code inside the for loop is actually the apply(..) method of the closure (a scala.Function1 in our case). Since that code belongs in another method normally a return statement present in that code would signal the return of that method.

This is not the case for Scala’s closures and when a return statement is present in a closure, the closure instead of returning at the point of invocation it also triggers the return of the enclosing method. This behavior is called non-local return and it is essential for creating control-flow abstractions using closures.

Because the JVM does not have native support for non-local return Scala implements it by throwing a Throwable. When a closure contains a return statement then that closure when executed instead of returning normally it throws a Throwable of type scala.runtime.NonLocalReturnControl that contains the returned value of the closure. The NonLocalReturnControl is then caught at the enclosing method and closure’s returned value is returned from the enclosing method. This is why when we try to define a closure that contains a return statement outside of a method the compiler will complain with an error because it cannot find where to generate the catch block for the NonLocalReturnControl thrown from the closure.

With the knowledge of how non-local return is implemented in Scala, we can now see that in the containsEven method the NonLocalReturnControl that gets thrown from the return true statement in the for loop gets caught by our case e => () pattern and gets ignored. So regardless of the input containsEven will always return false. To fix this we must simply catch NumberFormatException and not Throwable.

def containsEven(nums: String*): Boolean = {
  try {
    for (i <- nums) {
      if (i.toInt % 2 == 0)
        return true
    }
  } catch { case e: NumberFormatException => () }
  false
}

Catching Throwable can be a source of frequent bugs because even with the knowledge of how non-local return is implemented it is very likely that you will miss a closure with non-local return and introduce a bug. Furthermore Scala implements and other control structures by throwing Throwable like break and continuations making it even harder.

In the rare case when you absolutely need to catch Throwable the correct way to do it is the following:

import scala.util.control.ControlThrowable

try {
  codeThatMayThrowThrowable()
} catch {
  case e: ControlThrowable => throw e
  case e => handleThrowable(e)
}

In the above code we re-throw a Throwable if it is used for control-flow and handle all others. The scala.util.control.ControlThrowable is the Throwable that is extended by all throwables that are used for control flow in Scala.