Option, Either and Try

Scala · Practical · 14 min

Typed error handling makes failure paths visible in signatures and composable.

Idiomatic Scala avoids null and avoids exceptions for expected failures. Three types make absence and failure explicit:

  • Option[A] is Some(a) or None. Use it for values that may be missing: map.get(key), list.find(...), headOption. Wrap Java values that may be null with Option(x).
  • Either[E, A] is Left(error) or Right(value). It is right-biased: map and flatMap work on the Right and pass a Left through unchanged. Use it when the caller needs to know why something failed, with a typed error (often an enum).
  • Try[A] is Success(a) or Failure(throwable). Try(expr) catches non-fatal exceptions, which makes it the bridge from exception-throwing (often Java) code.

All three share an API: map, flatMap, filter, getOrElse, fold, foreach and conversions like toOption, toEither and toRight(err). Pattern matching works too.

Don't call .get on an Option or Try: it throws when empty and defeats the purpose. Chain transformations and decide at the edge of the program what to do with the error. Chaining several steps that can fail is cleanest with a for comprehension (next lesson).

Example

import scala.util.{Try, Success, Failure}

val ages = Map("ana" -> 31)
println(ages.get("ana").map(_ + 1))         // Some(32)
println(ages.get("bo").getOrElse(0))        // 0
println(Option(null: String))               // None

enum ParseError:
  case Empty
  case NotANumber(input: String)

def parse(s: String): Either[ParseError, Int] =
  if s.isEmpty then Left(ParseError.Empty)
  else s.toIntOption.toRight(ParseError.NotANumber(s))

println(parse("42").map(_ * 2))             // Right(84)
println(parse("x4").map(_ * 2))             // Left(NotANumber(x4))
println(parse("").fold(e => s"error: $e", n => s"ok $n"))  // error: Empty

val t = Try("12a".toInt)
t match
  case Success(n)  => println(n)
  case Failure(ex) => println(ex.getClass.getSimpleName)  // NumberFormatException
println(Try(10 / 2).toOption)               // Some(5)

Remember

  • Option for absence, Either for typed errors, Try to catch exceptions
  • All support map, flatMap, getOrElse and fold
  • Either is right-biased: map works on the success value
  • Never call .get; handle the empty case explicitly

Watch out for

  • Wrapping everything in Try, losing the specific error type that Either with an error enum would give.

Check yourself

  1. What does Left("oops").map((x: Int) => x + 1) return?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.scala-lang.org/scala3/book/fp-functional-error-handling.html