6 minutes read

Imagine that we have an application through which a customer can book a table in a restaurant. The client can enter their name and booking date:

def book(name: String, month: Int, dayOfMonth: Int): Unit =
  println(s"Table 42 is booked for $month.$dayOfMonth for $name")

But what if the user enters an incorrect date? For example, they might put month 15 by mistake. There has to be a way to deal with such mishaps.

Let's figure out which constructions in Scala help us work out exceptional situations!

Throwing exceptions

Scala has a keyword called throw. It allows you to complete the execution of a function in any part of it. At the same time, throw signals to the entire program that an exceptional situation has occurred.

This is how we can throw an exception:

throw Exception("You can write an error message here")

Exception is a common type for errors. There are also more specific ones. Here are some examples:

  • UnsupportedOperationException may show that an invalid operation is taking place.
  • NoSuchElementException can happen if we try to get a value from an empty entity.
  • IllegalArgumentException shows that we've received an invalid argument.

The last exception suits us best! For simplicity, we will only check the validity of the month:

def book(name: String, month: Int, dayOfMonth: Int): Unit =
  if month < 1 || month > 12 then
    throw IllegalArgumentException(s"Incorrect month: $month!")
  else
    println(s"Table number 42 is booked for $month.$dayOfMonth for $name")

Catching exceptions

Now, if we pass an incorrect month to the function, the program will terminate with an error message. How do we prevent termination? There is a try ... catch construction for this. Let's see how it works in detail.

We can wrap up a dangerous calculation and describe which error we want to catch using pattern matching. We put a message in the error, so we can print it using .getMessage:

try {
  book("Sully", 100, 1)
} catch {
  case ex: IllegalArgumentException => println(ex.getMessage)
}

It is not necessary to call the function in try directly. There may be a large number of calculations in this block. When we use catch, it's like we're entering a labyrinth with a ball of thread. As soon as an exception occurs somewhere in the labyrinth, we immediately go back following the thread and process the error.

If the code inside try throws different exceptions, we can catch them all as follows:

try {
  val month = getMonthInput()
  book("Sully", month, 1)
} catch {
  case ex: IllegalArgumentException => println(ex.getMessage)
  case ex: NoSuchElementException   => println("Could not get the month value.")
  case _: Exception                 => println("Something went wrong!")
}

Accordingly, case _: Exception => will catch all exceptions that may occur.

In Scala 3 quotation marks can be omitted where it makes code more readable:

try
  val month = getMonthInput()
  book("Sully", month, 1)
catch
  case ex: IllegalArgumentException => println(ex.getMessage)
  case ex: NoSuchElementException   => println("Could not get the month value.")
  case _: Exception                 => println("Something went wrong!")

Finally

The try ... catch construction can be supplemented with the finally keyword. In this block, we can do an action that is guaranteed to happen either after the code in try, or after error handling.

For example, we have shared counters of attempted bookings and successful bookings. We want to update the successful counter only after the reservation is complete. We will always update the attempt counter in finally:

var successfulBookingCounter: Int = 0
var bookingAttemptCounter: Int    = 0

try
  book("Sully", 100, 1)
  successfulBookingCounter += 1 // Will not happen in case of an error
catch
  case ex: IllegalArgumentException => println(ex.getMessage)
finally
  bookingAttemptCounter += 1 // Guaranteed to happen

It is important to understand that try ... catch is an expression that returns a value. finally will not affect the type of this value:

val name: String =
  try
    val clientName: String = getClientName()
    book(clientName, 100, 1)
    clientName
  catch
    case _: Exception => "undefined"
  finally
    println("println returns the Unit type, but the result is still String")

Conclusion

In this topic, we got acquainted with exceptions and handling them in Scala. Now you know that functions can terminate with errors using throw, and you can catch these errors using try ... catch. And if you need to do a guaranteed action after a line of code or an error, you can use the finally keyword.

In real programs, errors often occur, but now you can handle them with ease!

5 learners liked this piece of theory. 0 didn't like it. What about you?
Report a typo