Scala Error Handling

From WikiOD

Either[edit | edit source]

Different data types for error/success

def getPersonFromWebService(url: String): Either[String, Person] = {

    val response = webServiceClient.get(url)

    response.webService.status match {
        case 200 => {
            val person = parsePerson(response)
            if(!isValid(person)) Left("Validation failed")
            else Right(person)

        case _ => Left(s"Request failed with error code $response.status")

Pattern matching on Either value

getPersonFromWebService("http://some*") match {
    case Left(errorMessage) => println(errorMessage)
    case Right(person) => println(person.surname)

Convert Either value to Option

val maybePerson: Option[Person] = getPersonFromWebService("").right.toOption

Try[edit | edit source]

Using Try with map, getOrElse and flatMap:

import scala.util.Try

val i = Try("123".toInt)     // Success(123) + 1).getOrElse(321)  // 124

val j = Try("abc".toInt)     // Failure(java.lang.NumberFormatException) + 1).getOrElse(321)  // 321

Try("123".toInt) flatMap { i =>
    .map(_ + i)
}                            // Success(357)

Using Try with pattern matching:

Try(parsePerson("John Doe")) match {
    case Success(person) => println(person.surname)
    case Failure(ex) => // Handle error ...

Option[edit | edit source]

The use of null values is strongly discouraged, unless interacting with legacy Java code that expects null. Instead, Option should be used when the result of a function might either be something (Some) or nothing (None).

A try-catch block is more appropriate for error-handling, but if the function might legitimately return nothing, Option is appropriate to use, and simple.

An Option[T] can either be Some(value) (contains a value of type T) or None:

def findPerson(name: String): Option[Person]

If no person is found, None can be returned. Otherwise, an object of type Some containing a Person object is returned. What follows are ways to handle an object of type Option.

Pattern Matching[edit | edit source]

findPerson(personName) match {
    case Some(person) => println(person.surname)
    case None => println(s"No person found with name $personName")

Using map and getOrElse[edit | edit source]

val name = findPerson(personName).map(_.firstName).getOrElse("Unknown")
println(name) // Prints either the name of the found person or "Unknown"

Using fold[edit | edit source]

val name = findPerson(personName).fold("Unknown")(_.firstName)
// equivalent to the map getOrElse example above.

Converting to Java[edit | edit source]

If you need to convert an Option type to a null-able Java type for interoperability:

val s: Option[String] = Option("hello")
s.orNull           // "hello": String
s.getOrElse(null)  // "hello": String

val n: Option[Int] = Option(42)
n.orNull           // compilation failure (Cannot prove that Null <:< Int.)
n.getOrElse(null)  // 42

Handling Errors Originating in Futures[edit | edit source]

When an exception is thrown from within a Future, you can (should) use recover to handle it.

For instance,

def runFuture: Future = Future { throw new FairlyStupidException }

val itWillBeAwesome: Future = runFuture

...will throw an Exception from within the Future. But seeing as we can predict that an Exception of type FairlyStupidException with a high probability, we can specifically handle this case in an elegant way:

val itWillBeAwesomeOrIllRecover = runFuture recover { 
    case stupid: FairlyStupidException => 
         BadRequest("Another stupid exception!") 

As you can see the method given to recover is a PartialFunction over the domain of all Throwable, so you can handle just a certain few types and then let the rest go into the ether of exception handling at higher levels in the Future stack.

Note that this is similar to running the following code in a non-Future context:

def runNotFuture: Unit = throw new FairlyStupidException

try {
} catch {
    case e: FairlyStupidException => BadRequest("Another stupid exception!")

It is really important to handle exceptions generated within Futures because much of the time they are more insidious. They don't get all in your face usually, because they run in a different execution context and thread, and thus do not prompt you to fix them when they happen, especially if you don't notice anything in logs or the behavior of the application.

Using try-catch clauses[edit | edit source]

In addition to functional constructs such as Try, Option and Either for error handling, Scala also supports a syntax similar to Java's, using a try-catch clause (with a potential finally block as well). The catch clause is a pattern match:

try { 
  // ... might throw exception
} catch {
  case ioe: IOException => ... // more specific cases first
  case e: Exception => ...
  // uncaught types will be thrown
} finally {
  // ...

Convert Exceptions into Either or Option Types[edit | edit source]

To convert exceptions into Either or Option types, you can use methods that provided in scala.util.control.Exception

import scala.util.control.Exception._

val plain = "71a"
val optionInt: Option[Int] = catching(classOf[java.lang.NumberFormatException]) opt { plain.toInt }
val eitherInt = Either[Throwable, Int] = catching(classOf[java.lang.NumberFormatException]) either { plain.toInt }