Skip to content

Scala Exception Handling

Like Java and other languages, Scala uses exceptions to handle errors that disrupt the normal flow of program execution. A method can signal an error by throwing an exception instead of returning a value.

Scala does not have checked exceptions. This means the compiler doesn’t force you to declare or catch specific exceptions, offering more flexibility but requiring careful consideration of potential runtime errors.

Throwing an exception is identical to Java: you create an exception object and use the throw keyword.

def checkAge(age: Int): Unit = {
if (age < 0) {
throw new IllegalArgumentException(s"Age cannot be negative: $age")
}
println(s"Age $age is valid.")
}
// Example usage:
// checkAge(25) // Prints "Age 25 is valid."
// checkAge(-5) // Throws IllegalArgumentException

Scala uses a try/catch block to handle exceptions. The catch block utilizes pattern matching to handle different types of exceptions.

import java.io.{FileNotFoundException, FileReader, IOException}
import scala.util.Using // Recommended for resource management
@main def CatchExceptionDemo(): Unit = {
try {
// Using 'Using' ensures the FileReader is closed automatically
Using(new FileReader("input.txt")) { reader =>
// Process the file content... (not shown)
println("File opened successfully.")
}
} catch {
case ex: FileNotFoundException =>
println(s"Error: File not found - ${ex.getMessage}")
case ex: IOException =>
println(s"Error: I/O problem - ${ex.getMessage}")
case ex: Exception => // Catch broader exceptions last
println(s"An unexpected error occurred: ${ex.getMessage}")
}
}

Running this code (assuming input.txt doesn’t exist):

$ scala CatchExceptionDemo
Error: File not found - input.txt (The system cannot find the file specified)

The catch block matches the thrown exception against the case patterns in order. FileNotFoundException is caught first. If an IOException (that isn’t a FileNotFoundException) occurred, the second case would match. The final case ex: Exception acts as a catch-all for other potential exceptions.

It’s crucial to release resources like files or network connections, even if exceptions occur.

  1. scala.util.Using (Recommended): Introduced in Scala 2.13, Using provides a functional and safe way to manage resources that have a close() method. It ensures close() is called regardless of whether the operation succeeds or throws an exception. (See the example above).

  2. finally Clause: You can use a traditional finally block, which always executes after the try block (and any catch block that runs), regardless of whether an exception was thrown or not. It’s suitable for cleanup code.

import java.io.{FileNotFoundException, FileReader, IOException}
@main def FinallyDemo(): Unit = {
var reader: Option[FileReader] = None // Use Option to handle potential null
try {
reader = Some(new FileReader("input.txt"))
println("File opened (using finally example).")
// Process file...
} catch {
case ex: FileNotFoundException =>
println("File not found (finally example).")
case ex: IOException =>
println("IO Exception (finally example).")
} finally {
println("Entering finally block...")
reader.foreach { r =>
try { r.close() } catch { case _: IOException => /* Handle close error? */ }
println("FileReader closed.")
}
}
}

Output (if file not found):

$ scala FinallyDemo
File not found (finally example).
Entering finally block...

While finally works, Using is generally preferred for its conciseness and better integration with functional programming styles.

Idiomatic Scala often favors functional approaches to error handling over exceptions for recoverable errors. Consider using scala.util.Try, scala.util.Either, or Option to represent computations that might fail:

  • Option[T]: Represents a value that might be present (Some[T]) or absent (None). Good for optional results.
  • Try[T]: Represents a computation that results in either a success (Success[T]) containing a value or a failure (Failure[Throwable]) containing an exception. Useful for wrapping code that might throw exceptions.
  • Either[A, B]: Represents a value that is one of two types, conventionally Left[A] for an error and Right[B] for a success. More general than Try as the error type isn’t fixed to Throwable.
import scala.util.{Try, Success, Failure}
import scala.io.Source
def readFileContent(path: String): Try[String] = Try {
Using.resource(Source.fromFile(path))(_.mkString)
}
@main def TryDemo(): Unit = {
readFileContent("input.txt") match {
case Success(content) => println(s"File content:\n$content")
case Failure(ex) => println(s"Failed to read file: ${ex.getMessage}")
}
readFileContent("existing_file.txt") match { // Assuming this file exists
case Success(content) => println(s"Successfully read: ${content.take(20)}...")
case Failure(_) => println("This shouldn't happen if file exists")
}
}

Using Try, Either, or Option allows you to handle potential failures explicitly as part of the normal program flow, often leading to more robust and composable code.