Scala Option, Either, and Try: Functional Error Handling

TT

Scala Option, Either, and Try: Functional Error Handling

Idiomatic Scala avoids null and exceptions for expected failure cases. Instead, it uses three powerful types: Option for values that may or may not exist, Either for operations that can fail with an error, and Try for wrapping code that might throw exceptions. This module covers all three.

Option: Replacing null

Option[A] has two subtypes:

  • Some(value) — contains a value
  • None — represents absence of a value
scala
def findUser(id: Int): Option[String] = {
  val users = Map(1 -> "Alice", 2 -> "Bob")
  users.get(id)
}

val user = findUser(1)   // Some(Alice)
val missing = findUser(99)  // None

// Pattern matching
user match {
  case Some(name) => println(s"Found: $name")
  case None       => println("Not found")
}

Chaining with map and flatMap

scala
val upper: Option[String] = findUser(1).map(_.toUpperCase)
println(upper)  // Some(ALICE)

val missing: Option[String] = findUser(99).map(_.toUpperCase)
println(missing)  // None — map is a no-op on None

getOrElse and fold

scala
val name = findUser(1).getOrElse("Anonymous")  // "Alice"
val missing = findUser(99).getOrElse("Anonymous")  // "Anonymous"

// fold: combine the two cases in one call
val result = findUser(1).fold("Not found")(name => s"Hello, $name")
println(result)  // Hello, Alice

flatMap for chained lookups

scala
def findDepartment(userId: Int): Option[String] =
  Map(1 -> "Engineering", 2 -> "Marketing").get(userId)

val dept = findUser(1).flatMap(name => findDepartment(1))
println(dept)  // Some(Engineering)

Either: Typed Error Handling

Either[L, R] represents one of two possible values:

  • Left(error) — the failure case (by convention)
  • Right(value) — the success case
scala
def divide(a: Int, b: Int): Either[String, Double] =
  if (b == 0) Left("Division by zero")
  else Right(a.toDouble / b)

val result = divide(10, 2)   // Right(5.0)
val error  = divide(10, 0)   // Left(Division by zero)

result match {
  case Right(value) => println(s"Result: $value")
  case Left(err)    => println(s"Error: $err")
}

Chaining Either with map and flatMap

Either is right-biased in Scala 2.12+ — map and flatMap operate on the Right value:

scala
def parseAge(s: String): Either[String, Int] =
  s.toIntOption match {
    case Some(n) if n >= 0 && n <= 150 => Right(n)
    case Some(_)                        => Left("Age out of range")
    case None                           => Left(s"'$s' is not a number")
  }

def validateUser(name: String, ageStr: String): Either[String, String] =
  for {
    age <- parseAge(ageStr)
    validated <- if (name.nonEmpty) Right(s"$name, age $age") else Left("Name is empty")
  } yield validated

println(validateUser("Alice", "30"))  // Right(Alice, age 30)
println(validateUser("", "30"))       // Left(Name is empty)
println(validateUser("Bob", "abc"))   // Left('abc' is not a number)

Try: Wrapping Exception-Throwing Code

Try[A] wraps code that might throw an exception:

  • Success(value) — computation succeeded
  • Failure(exception) — computation threw an exception
scala
import scala.util.{Try, Success, Failure}

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

val good = parseNumber("42")    // Success(42)
val bad  = parseNumber("abc")   // Failure(NumberFormatException)

good match {
  case Success(n)   => println(s"Parsed: $n")
  case Failure(err) => println(s"Error: ${err.getMessage}")
}

Converting between Try, Option, and Either

scala
// Try to Option
val opt: Option[Int] = parseNumber("42").toOption  // Some(42)

// Try to Either
val either: Either[Throwable, Int] = parseNumber("42").toEither  // Right(42)

// Option to Either
val e: Either[String, Int] = findUser(1).toRight("User not found")

Choosing Between Option, Either, and Try

TypeUse When
OptionA value may or may not exist; no error information needed
EitherAn operation can fail; you want a descriptive error type
TryWrapping Java/library code that throws exceptions

Frequently Asked Questions

Q: Is Option just Scala's version of Java's Optional? They are similar but Option comes first — Java's Optional was introduced in Java 8 (2014), partly inspired by Scala's Option and similar types in functional languages. The key difference is that Scala's Option is deeply integrated with the language's type system, for-comprehensions, and collection operations, making it more composable.

Q: Why does Scala use Right for success in Either? It's a convention — Right sounds like "right" (correct), and Left sounds like "what's left after the right answer failed". More practically, Scala's Either is right-biased: map, flatMap, and for-comprehensions all operate on the Right value. The convention aligns with the language's bias.

Q: When should I use Try vs Either? Use Try when you're calling code that throws exceptions (especially Java library code) and want to catch all exceptions automatically. Use Either when you're writing your own error-handling logic with specific, typed error cases. Either[MyError, A] makes the error type explicit and lets callers handle different errors differently — Try[A] just captures Throwable.


Part of Scala Mastery Course — Module 11 of 22.