Handling Exceptions using Try/Catch/Finally in Scala

Julien Truffaut image

Julien Truffaut

25 September 2023

Exception handling is a critical aspect of software development, and Scala provides powerful tools to manage exceptions effectively. In this guide, we’ll dive into the Try/Catch/Finally construct.

Try/Catch/Finally: An Overview

Try/Catch/Finally is a low level expression for handling exceptions in Scala. Let’s explore how it works using a simple example:

def parseNumber(line: String): Option[Int] =
  try {
    val number = line.toInt
    Some(number)
  } catch {
    case e: NumberFormatException =>
      None
  }

parseNumber("1234")  // Option[Int] = Some(1234)
parseNumber("-1")    // Option[Int] = Some(-1)
parseNumber("hello") // Option[Int] = None

Here’s a breakdown of the code:

  • parseNumber is a function that attempts to transform a String into an Int.
  • if the String isn’t valid, the function returns None.
  • the try block is where we put the code that can throw an Exception.
  • The catch block specifies which exceptions we want to handle.
  • if an Exception is thrown but not caught by the catch block, it propagates up the call stack.

Catching Multiple Exceptions

The catch block behaves like a normal pattern match on a Throwable, the top level class of all errors and exceptions. This enables us to handle different exceptions in distinct ways:

try {
  // Risky code
} catch {
  case e: NumberFormatException => // Handle NumberFormatException
  case e: IOException           => // Handle IOException
}

Catching All Exceptions

It’s also possible to catch all exceptions by not specifying an exception type:

try {
  // Risky code
} catch {
  case e => // Handle any Throwable (equivalent to case e: Throwable)
}

Fatal errors

While catching exceptions is essential, not all errors are recoverable. For example, an OutOfMemoryError is typically fatal and should not be handled. Scala provides a convenient function called NonFatal to detect if an exception is non-fatal:

import scala.util.control.NonFatal

try {
  // Risky code
} catch {
  case NonFatal(e) => // Handle non-fatal exceptions
}

This approach ensures that only non-fatal exceptions are caught, leaving fatal errors unhandled.

Using Try for Exception Handling

Alternatively, we can use Try from the scala.util package when we want to catch all non-fatal errors.

import scala.util.Try

def parseNumber(line: String): Try[Int] =
  Try(line.toInt)

parseNumber("1234")  // Try[Int] = Success(1234)
parseNumber("-1")    // Try[Int] = Success(-1)
parseNumber("hello") // Try[Int] = Failure(java.lang.NumberFormatException: For input string: "hello")

Try encapsulates the result of an operation, either as a Success or a Failure similar to Option or Either.

In summary, Scala offers various mechanisms for handling exceptions effectively. Whether you choose the traditional Try/Catch/Finally approach or leverage the Try class is essential for writing robust and reliable Scala code. In the next part, we’ll explore the finally block for running post-processing operations, enhancing your exception management skills. Stay tuned!