Scala Exception Handling
Exception handling lets your program respond to errors gracefully instead of crashing. Scala inherits Java's exception model — you use try, catch, and finally — but Scala also provides functional alternatives like Try, Either, and Option that treat errors as values. This topic covers both approaches.
try / catch / finally
try
val result = 10 / 0
println(result)
catch
case e: ArithmeticException =>
println(s"Math error: ${e.getMessage}")
case e: Exception =>
println(s"General error: ${e.getMessage}")
finally
println("This always runs, error or not")
// Math error: / by zero
// This always runs, error or not
try block
│
├── Success → continues normally → finally block
│
└── Exception thrown
│
├── catch case matches? → handle it → finally block
└── no match → exception propagates up → finally block
try as an Expression
Like everything in Scala, try/catch is an expression and returns a value:
val result: Int =
try "42".toInt
catch
case _: NumberFormatException => -1
println(result) // 42
val bad: Int =
try "abc".toInt
catch
case _: NumberFormatException => -1
println(bad) // -1
Catching Multiple Exception Types
def readData(input: String): Int =
try
val num = input.trim.toInt
require(num > 0, "Number must be positive")
num
catch
case _: NumberFormatException =>
println(s"'$input' is not a number")
0
case e: IllegalArgumentException =>
println(s"Validation failed: ${e.getMessage}")
0
case e: Exception =>
println(s"Unexpected: ${e.getClass.getSimpleName}")
0
println(readData("42")) // 42
println(readData("abc")) // 'abc' is not a number → 0
println(readData("-5")) // Validation failed: requirement failed → 0
finally — Guaranteed Cleanup
def processFile(filename: String): Unit =
var opened = false
try
println(s"Opening $filename")
opened = true
if filename.endsWith(".bad") then
throw new RuntimeException("Corrupt file!")
println("File processed successfully")
catch
case e: RuntimeException =>
println(s"Error: ${e.getMessage}")
finally
if opened then println(s"Closing $filename")
processFile("data.csv")
// Opening data.csv
// File processed successfully
// Closing data.csv
processFile("data.bad")
// Opening data.bad
// Error: Corrupt file!
// Closing data.bad
Throwing Exceptions
def divide(a: Int, b: Int): Int =
if b == 0 then throw new ArithmeticException("Cannot divide by zero")
a / b
def requirePositive(n: Int): Int =
require(n > 0, s"Expected positive, got $n") // throws IllegalArgumentException
n
def fetchUser(id: Int): String =
if id <= 0 then throw new IllegalArgumentException(s"Invalid ID: $id")
s"User_$id"
Custom Exceptions
class InsufficientFundsException(val amount: Double, val available: Double)
extends Exception(f"Cannot withdraw ₹$amount%.2f, only ₹$available%.2f available"):
def shortfall: Double = amount - available
class AccountNotFoundException(val accountId: String)
extends Exception(s"Account '$accountId' not found")
def withdraw(accountId: String, amount: Double): Unit =
val balance = 500.0 // simulated
if accountId.isEmpty then
throw AccountNotFoundException(accountId)
if amount > balance then
throw InsufficientFundsException(amount, balance)
println(f"Withdrew ₹$amount%.2f")
try withdraw("ACC001", 1000.0)
catch
case e: InsufficientFundsException =>
println(s"${e.getMessage} (shortfall: ₹${e.shortfall}%.2f)")
case e: AccountNotFoundException =>
println(e.getMessage)
// Cannot withdraw ₹1000.00, only ₹500.00 available (shortfall: ₹500.00)
Functional Error Handling — Try
import scala.util.{Try, Success, Failure}
def safeDiv(a: Int, b: Int): Try[Int] = Try(a / b)
safeDiv(10, 2) match
case Success(v) => println(s"Result: $v")
case Failure(e) => println(s"Error: ${e.getMessage}")
// Result: 5
val chain = Try("100".toInt).map(_ * 2).map(_ + 50)
println(chain) // Success(250)
val bad = Try("abc".toInt).map(_ * 2)
println(bad) // Failure(NumberFormatException: ...)
When to Use Which Approach
Situation Approach
────────────────────────────────────── ──────────────────────────
Calling Java code that throws try/catch or Try { }
Need guaranteed cleanup (I/O) try/finally
Error carries no extra info needed Option
Error carries a message or code Either[String, A]
Exception-throwing code as value Try[A]
Multiple error types to handle try/catch with cases
Chaining with recover
import scala.util.Try
val result = Try("not-a-number".toInt)
.recover { case _: NumberFormatException => 0 }
.map(_ + 100)
println(result) // Success(100)
// recoverWith returns another Try
val result2 = Try("bad".toInt)
.recoverWith { case _ => Try("42".toInt) }
println(result2) // Success(42)
Do Not Swallow Exceptions
// BAD — hides all errors silently
try doSomething()
catch case _: Exception => () // Don't do this!
// BETTER — log and handle
try doSomething()
catch case e: Exception =>
println(s"Error in doSomething: ${e.getMessage}")
// and/or rethrow, return a default, or use Try/Either
Silent exception swallowing makes debugging nearly impossible. Always log, handle, or propagate exceptions so failures are visible and traceable.
