How We Handle Exception In Scala!

Reading Time: 3 minutes

Introduction

In this blog, we’re going to talk about exception handling in Scala. We’ll explore different ways of handling them, using different constructs provided by the language.

What’s an Exception?

An exception is an event that changes the normal flow of a program.

It is a mechanism what is used to handle abnormal conditions. You can also avoid termination of your program unexpectedly.

Exceptions are checked or uncheckedScala only allows unchecked exceptions, though. This means that, at compile-time, we won’t be able to know if a method is throwing an exception we are not handling.

A basic example of Exception Handling

Let’s write a simple calculator to show different ways of handling exceptions in Scala. It’ll only add positive integer numbers:

object CalculatorExceptions {
  class IntOverflowException extends RuntimeException
  class NegativeNumberException extends RuntimeException
}

object Calculator {
  import CalculatorExceptions._

  def sum(a: Int, b: Int): Int = {
    if (a < 0 || b < 0) throw new NegativeNumberException
    val result = a + b
    if (result < 0) throw new IntOverflowException
    result
  }
}

Our method could throw a NegativeNumberException if one of the addends is negative, or IntOverflowException if the sum overflows the Int range.

When using our calculator, we’ll have to consider how to handle those exceptions.

try/catch/finally

A basic way we can handle exceptions in Scala is the try/catch/finally construct, really similar to the Java one.

In the following example, to make testing easier, we’ll return a different negative error code for each exception caught:

def tryCatch(a: Int, b: Int): Int = {
  try {
    return Calculator.sum(a,b)
  } catch {
    case e: IntOverflowException => -1
    case e: NegativeNumberException => -2
  } finally {
    // This block will always be invoked
    println("Calculation done!")
  }
}

In our example, we’re returning different error codes depending on the exception we caught.

try/Success/Failure

Try[T] is an Algebraic Data Type, whose instances are Success[T] and Failure[T].

Let’s rewrite our tryCatch method using it:

def trySuccessFailure(a: Int, b: Int): Try[Int] = Try {
  Calculator.sum(a,b)
}

Now, we can use some tests to show how the result of trySuccessFailure is used in a functional style:

"trySuccessFailure" should "handle NegativeNumberException" in {
  import CalculatorExceptions._
  val result = trySuccessFailure(-1,-2)
  result match {
    case Failure(e) => assert(e.isInstanceOf[NegativeNumberException])
    case Success(_) => fail("Should fail!")
  }
}

it should "handle IntOverflowException" in {
  import CalculatorExceptions._
  val result = trySuccessFailure(Int.MaxValue,1)
  result match {
    case Failure(e) => assert(e.isInstanceOf[IntOverflowException])
    case Success(_) => fail("Should fail!")
  }
}

it should "return the correct sum" in {
  import CalculatorExceptions._
  val result = trySuccessFailure(3,2)
  result match {
    case Failure(e) => fail("Should succed!")
    case Success(result) => assert(result == 5)
  }
}

Our method will return either a Succes or a Failure class. Using a pattern match, we can easily handle the result of our function.

Catch

Another way of catching exceptions comes from the scala.util.control.Exception object. Let’s use a catch object to handle our Calculator.sum:

def catchObjects(a: Int, b: Int): Try[Int] = allCatch.withTry {
  Calculator.sum(a,b)
}

The allCatch.withTry object allows us to catch all the exceptions and handle them with a Try. The above code will behave exactly as the trySuccessFailure we previously implemented.

scala.util.control.Exception also provides out-of-the-box opt and either to wrap the exception in, respectively, an Option or an Either.

An interesting feature of catch objects is the possibility of defining custom matchers. To see how simple they are to define, we can write one that only handles NegativeNumberException:

val myCustomCatcher = catching(classOf[NegativeNumberException])

def customCatchObjects(a: Int, b: Int): Try[Int] = myCustomCatcher.withTry{
  Calculator.sum(a,b)
}

Moreover, we can use the scala.util.control.Exception.ignoring() catch object to catch and ignore the specified exceptions:

def ignoringAndSum(a: Int, b: Int) =
  ignoring(classOf[NegativeNumberException], classOf[IntOverflowException]) {
    println(s"Sum of $a and $b is equal to ${Calculator.sum(a, b)}")
  }

This will execute the pass block of code while ignoring the mentioned exceptions.

Let’s use some tests to show the behavior of our new matcher:

"customCatchObjects" should "handle NegativeNumberException" in {
  import CalculatorExceptions._
  val result = customCatchObjects(-1,-2)
  result match {
    case Failure(e) => assert(e.isInstanceOf[NegativeNumberException])
    case Success(_) => fail("Should fail!")
  }
}

it should "handle IntOverflowException" in {
  import CalculatorExceptions._
  assertThrows[IntOverflowException] {
    customCatchObjects(Int.MaxValue,1)
  }
}

it should "return the correct sum" in {
  import CalculatorExceptions._
  val result = customCatchObjects(3,2)
  result match {
    case Failure(e) => fail("Should succed!")
    case Success(result) => assert(result == 5)
  }
}
it should "ignore specified exceptions" in {
  Examples.ignoringAndSum(-1, -2)
}

As we can see, in case an IntOverflowException exception is thrown, it will not be handled.

Catch objects can be handy to centralize the exception handling logic and avoid repetitious code.

Conclusion

In this blog, we talked about Scala’s support for exception handling.

When choosing between catch objects and Try/Success/Failurethe trade-off is between code accessibility and code reusability.

In Scala, they are definitively preferable to try/catch/finally since they provide an easier way to achieve functional composability.

References

knoldus

Written by 

My name is Harshal Dubey. I am working as a Software Intern in Scala Studio at Knoldus. My hobbies are playing volleyball, football and Travelling.