Exception Handling in Scala

When I started working on scala I had a tough time understanding how exception handling works in scala. I had some background in java and javascript and I had exclusively worked with imperative codebases till that time.
When I look back I think my understanding about exception handling in scala came in stages: In the first stage I figured out what are the different ways of exception handling in scala. After this stage I knew what are different ways but I had a difficult time using those appropriately. During this time I started asking questions: Why do we need more than one way to handle exceptions? Where to use which one ? How to make the application behave properly in case of unexpected conditions and at the same time code should not become too messy for others to understand ? etc.

In this blog we will first briefly look into what are the different ways of exception handling in scala and when shall we use those. Next we will see some more points which are like set of best practices regarding Exception handling in scala.

There are predominantly 3 ways to handle exceptions in scala: Try, Option and Either.

Try :

From scaladoc The Try type represents a computation that may either result in an exception, or return a successfully computed value .A successful completion of Try[T] will result in Success[T] while failure results in Failure[T], where T is the result type. Failure wraps non-fatal exceptions. An example will look like as below:

val res = Try { /*some computation which can fail*/}
res match{
case Success(value) => SuccessResponse(value)
case Failure(exception) => {
Logger.error(" Your Failure Message",exception)
FailedResponse(exception.getMessage)
}
}

SuccessResponse and FailedResponse are case classes.
Use Try when you care about success or failure and also you care about matching the type of Throwable, getting error messages or state.

Option:

Option[T] is a container for Zero (None) or One element (Some[T])of type T.
One of the examples is the get method of Map which returns Option[T].
Use Option when you only care about success or failure but do not care about any error message or state.

Either:

Either[A,B] allows you to return one of two different types. Conventionally, Right is success and Left is failure.
Try can be thought of as Either with Left as Throwable and Right as result. Or Similarly Option can be thought of as Either where Left represents failure (by convention) and Right is akin to Some.
An example will look like as below:

trait GenericUserError
case class UserNotFound(id: Long) extends GenericUserError
case class UserAccountBlocked(id: Long) extends GenericUserError
case class User(id: Long, name: String)
def getUser: Either[GenericUserError, User] = ???
getUser match{
case Left(UserNotFound(id)) => println(s"user $id not found")
case Right(user) => println(s"username is ${user.name}")
}

I have found some people have issues with using Either in codebase and I think they are right because of the following reasons:

After we have seen the different ways of error handling, below is a non exhaustive set of best practices for error handling in scala: