Error Handling: Exceptions and Try
Prerequisites: For-comprehensions
try, catch, finally
@main def exceptions(): Unit =
def parse(s: String): Int =
try s.trim.toInt
catch
case _: NumberFormatException => 0
finally println(s"parsed '$s'")
println(parse(" 42 "))
println(parse("abc"))
parsed ' 42 '
42
parsed 'abc'
0
tryis an expression — it returns the value of thetryblock or the matchingcatchcase.catchuses pattern matching on the exception type.finallyalways runs — for cleanup — and doesn't affect the result.
Throw with throw IllegalArgumentException("message"). Scala has no
checked exceptions: the compiler doesn't force you to catch anything.
Try: errors as values
Try[A] holds either Success(value) or Failure(exception), so
failure becomes a value you can pass around and transform:
import scala.util.{Try, Success, Failure}
@main def tries(): Unit =
val good = Try("42".toInt)
val bad = Try("abc".toInt)
println(good)
println(bad)
println(good.map(_ * 2).getOrElse(0))
println(bad.map(_ * 2).getOrElse(0))
bad match
case Success(n) => println(s"got $n")
case Failure(e) => println(s"failed: ${e.getMessage}")
val total = for
a <- Try("10".toInt)
b <- Try("5".toInt)
yield a / b
println(total)
println(total.flatMap(t => Try(t / 0)))
Success(42)
Failure(java.lang.NumberFormatException: For input string: "abc")
84
0
failed: For input string: "abc"
Success(2)
Failure(java.lang.ArithmeticException: / by zero)
Try catches non-fatal exceptions, so it's ideal for wrapping Java
APIs or parsing code that throws. toOption converts it to an Option
when you don't need the error.
When to use which
- Exceptions — truly unexpected problems (bugs, a missing config file at startup), and at the edges of your program.
- Try — wrapping code that throws, when you want to handle failure as a value.
- Either — expected failures with your own error type (next lesson).