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
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).
Tryis great when the error is aThrowable(like Java exceptions).Eitheris 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
- Wrap a risky operation in
Try(...)— it catches exceptions automatically and returnsSuccessorFailure. - Use
mapto transform the success value — it does nothing if theTryis aFailure. - Use
flatMapto chain anotherTry-returning operation. - For
Either, useLeftto represent an error andRightfor success. Use.mapon the right side and.left.mapon the left. - 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
.toOptionon aTryto getSome/None— it's the closest to Python'sOptional. Use.toEitherto convert toEither[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
Tryis aFailureand you call.get? It throws the original exception. Use.getOrElse(default)instead. - What if
EitherisLeftand you call.map? It does nothing — returns the sameLeft. Use.left.mapto transform the left side. - Why does my
flatMapchain returnAny? Type inference can break if you mixTryandEither. Be consistent — decide on one error type per chain. - What about nested exceptions?
Tryflattens automatically — aTryinside aTryis flattened byflatMap.
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
.getonFailure— it throws the original exception. Use.getOrElseor pattern match. - Mixing
TryandEitherin the same for-comprehension — type inference breaks. Stick to one type per chain. - Treating
Leftas failure andRightas success, but forgetting to use.left.mapfor errors — the left side is not automatically mapped. - Returning
NonefromTrywhen you meantFailure— useTry(...)to capture exceptions; returningNoneloses error details.
Variations
- Use
Optionwhen you only care about success/failure without error details — it's simpler but less informative. - Use
scala.util.control.Exception.catchingfor a more functional way to catch specific exceptions. - Use a custom sealed trait hierarchy with
Eitherfor more precise error handling (e.g.,sealed trait Error,case class NotFoundetc.).
Real-world use cases
- Parsing command-line arguments: wrap each parse in
Tryto 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 —
Tryfits. - Validating a form in a web app: return
Either[String, User]to report user-friendly validation errors without exceptions.
Key takeaways
Trywraps operations that throw exceptions intoSuccessorFailure, making errors values.Eitherlets you define your own error type, perfect for domain validation.- Both support
mapandflatMap— compose error-prone operations without nesting. - Use
getOrElseto provide fallbacks safely; never call.getonFailure. - Choose
Tryfor Java interop andEitherfor domain errors.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.