Try and Either in Scala

Learn to handle errors with Try and Either in Scala, built for Python developers. Step-by-step lesson with hands-on exercise, edge cases, and next steps.

Focus: handle errors with try and either

Sponsored

In Python, you're used to wrapping risky code in try/except blocks and hoping you remember to handle every possible exception. But as your codebase grows, scattered except clauses make it hard to reason about what can fail and how. In Scala, you get two powerful tools — Try and Either — that make error handling explicit, composable, and type-safe. This lesson shows you how to move from Python-style exceptions to functional error handling that will feel like a superpower once it clicks.

The problem this lesson solves

When you write a function that hits an API, reads a file, or parses user input, things can go wrong. In Python, you do this:

def safe_divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        return None

That returns None on failure — but None could also be a legitimate result. You lose the error reason. And if you forget to catch TypeError or KeyError, your whole app crashes.

Scala's Try and Either solve this by making failures values you can pass around, transform, and combine — without implicit try/catch nesting.

Core concept / mental model

Think of Try as a safe box that either contains a successful result or an exception. It's like a monadic version of Python's Optional but with error details attached.

Either is a left-or-right container: by convention, Left holds the error, Right holds the success. It's more flexible than Try — you can choose your own error type (a string, a custom case class, anything).

  • Try is great when the error is a Throwable (like Java exceptions).
  • Either is great when the error is a domain value (like a validation message).

Both are monads — they support map, flatMap, and for-comprehensions, so you can chain operations without deep nesting.

How it works step by step

  1. Wrap a risky operation in Try(...) — it catches exceptions automatically and returns Success or Failure.
  2. Use map to transform the success value — it does nothing if the Try is a Failure.
  3. Use flatMap to chain another Try-returning operation.
  4. For Either, use Left to represent an error and Right for success. Use .map on the right side and .left.map on the left.
  5. Recover from a failure using .getOrElse, .recover, or .toOption.

Hands-on walkthrough

Let's start with Try. Open your Scala REPL or a Main.scala file and run this:

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

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

val result1 = parseInt("42")
val result2 = parseInt("abc")

println(result1)  // Success(42)
println(result2)  // Failure(java.lang.NumberFormatException: For input string: "abc")

Expected output:

Success(42)
Failure(java.lang.NumberFormatException: For input string: "abc")

Now let's chain operations with flatMap:

val sum = for {
  a <- parseInt("10")
  b <- parseInt("20")
} yield a + b

println(sum)  // Success(30)

val failSum = for {
  a <- parseInt("10")
  b <- parseInt("bad")
} yield a + b

println(failSum)  // Failure(java.lang.NumberFormatException: ...)

Now, Either. Here's a validation example:

def validateAge(age: Int): Either[String, Int] =
  if (age >= 0 && age <= 150) Right(age)
  else Left(s"Invalid age: $age")

val age1 = validateAge(30)
val age2 = validateAge(-5)

println(age1)  // Right(30)
println(age2)  // Left(Invalid age: -5)

// Transform the right side
val message = age1.map(a => s"Age is $a")  // Right(Age is 30)
println(message)

Pro tip: Use .toOption on a Try to get Some/None — it's the closest to Python's Optional. Use .toEither to convert to Either[Throwable, A] when you need a custom error type.

Compare options / when to choose what

Feature Try Either Python equivalent
Error type Always Throwable You choose (String, custom class) Exception subclasses
Expressiveness Limited to exceptions Rich domain errors N/A
Monad ops (map, flatMap) Yes Yes N/A
Best for Java interop, IO operations Validation, business logic errors try/except
Recover from failure .getOrElse, .recover .getOrElse, .left.map except clause

When to choose Try: When working with Java libraries that throw exceptions, or when the error is genuinely a Throwable (network, parsing, files).

When to choose Either: When you need to model expected failures (like validation errors) with your own types — more expressive and safer.

Troubleshooting & edge cases

  • What if Try is a Failure and you call .get? It throws the original exception. Use .getOrElse(default) instead.
  • What if Either is Left and you call .map? It does nothing — returns the same Left. Use .left.map to transform the left side.
  • Why does my flatMap chain return Any? Type inference can break if you mix Try and Either. Be consistent — decide on one error type per chain.
  • What about nested exceptions? Try flattens automatically — a Try inside a Try is flattened by flatMap.

What you learned & what's next

You now know how to handle errors with Try and Either — making failures explicit, composable, and type-safe. You can transform results with map, chain operations with flatMap, and choose the right tool for the job.

Next, we'll explore pattern matching — the perfect companion to Try and Either to extract values cleanly.

Practice recap

Try the following: write a function that reads a file path and returns Try[String] (file content). Then use a for-comprehension to parse an Int from the content, converting the Try to an Either[String, Int] at the end. Print the result. This ties together everything you learned.

Common mistakes

  • Using .get on Failure — it throws the original exception. Use .getOrElse or pattern match.
  • Mixing Try and Either in the same for-comprehension — type inference breaks. Stick to one type per chain.
  • Treating Left as failure and Right as success, but forgetting to use .left.map for errors — the left side is not automatically mapped.
  • Returning None from Try when you meant Failure — use Try(...) to capture exceptions; returning None loses error details.

Variations

  1. Use Option when you only care about success/failure without error details — it's simpler but less informative.
  2. Use scala.util.control.Exception.catching for a more functional way to catch specific exceptions.
  3. Use a custom sealed trait hierarchy with Either for more precise error handling (e.g., sealed trait Error, case class NotFound etc.).

Real-world use cases

  • Parsing command-line arguments: wrap each parse in Try to get a friendly error message instead of crashing.
  • Calling a third-party REST API where network/JSON errors need to be captured and handled gracefully — Try fits.
  • Validating a form in a web app: return Either[String, User] to report user-friendly validation errors without exceptions.

Key takeaways

  • Try wraps operations that throw exceptions into Success or Failure, making errors values.
  • Either lets you define your own error type, perfect for domain validation.
  • Both support map and flatMap — compose error-prone operations without nesting.
  • Use getOrElse to provide fallbacks safely; never call .get on Failure.
  • Choose Try for Java interop and Either for domain errors.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.