When just playing around with a new language, you might get away with simply ignoring the fact that something might go wrong. As soon you want to create anything serious, though, you can no longer run away from handling errors and exceptions in your code. The importance of how well a language supports you in doing so is often underestimated, for some reason or another.
Scala, as it turns out, is pretty well positioned when it comes to dealing with error conditions in an elegant way. In this article, I’m going to present Scala’s approach to dealing with errors, based on the
Try type, and the rationale behind it. I’m using features introduced with Scala 2.10 and ported back to Scala 2.9.3, so make sure your Scala version in SBT is at 2.9.3 or later.
Throwing and catching exceptions
Before going straight to Scala’s idiomatic approach at error handling, let’s first have a look at an approach that is more akin to how you are used to working with error conditions if you come from languages like Java or Ruby. Like these languages, Scala allows you to throw an exception:
1 2 3 4 5 6 7
Thrown exceptions can be caught and dealt with very similarly to Java, albeit using a partial function to specify the exceptions we want to deal with. Also, Scala’s
catch is an expression, so the following code returns the message of the exception:
1 2 3 4 5 6 7
Error handling, the functional way
Now, having this kind of exception handling code all over your code base can become ugly very quickly and doesn’t really go well with functional programming. It’s also a rather bad solution for applications with a lot of concurrency. For instance, if you need to deal with an exception thrown by an Actor that is executed on some other thread, you obviously cannot do that by catching that exception – you will want a possibility to receive a message denoting the error condition.
Hence, in Scala, it’s usually preferred to signify that an error has occurred by returning an appropriate value from your function.
Don’t worry, we are not going back to C-style error handling, using error codes that we need to check for by convention. Rather, in Scala, we are using a specific type that represents computations that may result in an exception.
In this article, we are confining ourselves to the
Try type that was introduced in Scala 2.10 and later backported to Scala 2.9.3. There is also a similar type, called
Either, which, even after the introduction of
Try, can still be very useful, but is more general.
The semantics of Try
The semantics of
Try are best explained by comparing them to those of the
Option type that was the topic of the previous part of this series.
Option[A] is a container for a value of type
A that may be present or not,
Try[A] represents a computation that may result in a value of type
A, if it is successful, or in some
Throwable if something has gone wrong. Instances of such a container type for possible errors can easily be passed around between concurrently executing parts of your application.
There are two different types of
Try: If an instance of
Try[A] represents a successful computation, it is an instance of
Success[A], simply wrapping a value of type
A. If, on the other hand, it represents a computation in which an error has occurred, it is an instance of
Failure[A], wrapping a
Throwable, i.e. an exception or other kind of error.
If we know that a computation may result in an error, we can simply use
Try[A] as the return type of our function. This makes the possibility explicit and forces clients of our function to deal with the possibility of an error in some way.
For example, let’s assume we want to write a simple web page fetcher. The user will be able to enter the URL of the web page they want to fetch. One part of our application will be a function that parses the entered URL and creates a
java.net.URL from it:
1 2 3
As you can see, we return a value of type
Try[URL]. If the given
url is syntactically correct, this will be a
Success[URL]. If the
URL constructor throws a
MalformedURLException, however, it will be a
To achieve this, we are using the
apply factory method on the
Try companion object. This method expects a by-name parameter of type
URL). For our example, this means that the
new URL(url) is executed inside the
apply method of the
Try object. Inside that method, non-fatal exceptions are caught, returning a
Failure containing the respective exception.
parseURL("http://danielwestheide.com") will result in a
Success[URL] containing the created URL, whereas
parseURL("garbage") will result in a
Failure[URL] containing a
Working with Try values
Try instances is actually very similar to working with
Option values, so you won’t see many surprises here.
You can check if a
Try is a success by calling
isSuccess on it and then conditionally retrieve the wrapped value by calling
get on it. But believe me, there aren’t many situations where you will want to do that.
It’s also possible to use
getOrElse to pass in a default value to be returned if the
Try is a
If the URL given by the user is malformed, we use the URL of DuckDuckGo as a fallback.
One of the most important characteristics of the
Try type is that, like
Option, it supports all the higher-order methods you know from other types of collections. As you will see in the examples to follow, this allows you to chain operations on
Try values and catch any exceptions that might occur, and all that in a very readable manner.
Mapping and flat mapping
Try[A] that is a
Success[A] to a
Try[B] results in a
Success[B]. If it’s a
Failure[A], the resulting
Try[B] will be a
Failure[B], on the other hand, containing the same exception as the
1 2 3 4
If you chain multiple
map operations, this will result in a nested
Try structure, which is usually not what you want. Consider this method that returns an input stream for a given URL:
1 2 3 4
Since the anonymous functions passed to the two
map calls each return a
Try, the return type is a
This is where the fact that you can
Try comes in handy. The
flatMap method on a
Try[A] expects to be passed a function that receives an
A and returns a
Try[B]. If our
Try[A] instance is already a
Failure[A], that failure is returned as a
Failure[B], simply passing along the wrapped exception along the chain. If our
Try[A] is a
flatMap unpacks the
A value in it and maps it to a
Try[B] by passing this value to the mapping function.
This means that we can basically create a pipeline of operations that require the values carried over in
Success instances by chaining an arbitrary number of
flatMap calls. Any exceptions that happen along the way are wrapped in a
Failure, which means that the end result of the chain of operations is a
Let’s rewrite the
inputStreamForURL method from the previous example, this time resorting to
1 2 3
Now we get a
Try[InputStream], which can be a
Failure wrapping an exception from any of the stages in which one may be thrown, or a
Success that directly wraps the
InputStream, the final result of our chain of operations.
Filter and foreach
Of course, you can also filter a
Try or call
foreach on it. Both work exactly as you would expect after having learned about
filter method returns a
Failure if the
Try on which it is called is already a
Failure or if the predicate passed to it returns
false (in which case the wrapped exception is a
NoSuchElementException). If the
Try on which it is called is a
Success and the predicate returns
Succcess instance is returned unchanged:
1 2 3
The function passed to
foreach is executed only if the
Try is a
Success, which allows you to execute a side-effect. The function passed to
foreach is executed exactly once in that case, being passed the value wrapped by the
The support for
filter means that you can also use for comprehensions in order to chain operations on
Try instances. Usually, this results in more readable code. To demonstrate this, let’s implement a method that returns the content of a web page with a given URL using for comprehensions.
1 2 3 4 5 6 7 8
There are three places where things can go wrong, all of them covered by usage of the
Try type. First, the already implemented
parseURL method returns a
Try[URL]. Only if this is a
Success[URL], we will try to open a connection and create a new input stream from it. If opening the connection and creating the input stream succeeds, we continue, finally yielding the lines of the web page. Since we effectively chain multiple
flatMap calls in this for comprehension, the result type is a flat
Please note that this could be simplified using
Source#fromURL and that we fail to close our input stream at the end, both of which are due to my decision to keep the example focussed on getting across the subject matter at hand.
At some point in your code, you will often want to know whether a
Try instance you have received as the result of some computation represents a success or not and execute different code branches depending on the result. Usually, this is where you will make use of pattern matching. This is easily possible because both
Failure are case classes.
We want to render the requested page if it could be retrieved, or print an error message if that was not possible:
1 2 3 4 5 6
Recovering from a Failure
If you want to establish some kind of default behaviour in the case of a
Failure, you don’t have to use
getOrElse. An alternative is
recover, which expects a partial function and returns another
recover is called on a
Success instance, that instance is returned as is. Otherwise, if the partial function is defined for the given
Failure instance, its result is returned as a
Let’s put this to use in order to print a different message depending on the type of the wrapped exception:
1 2 3 4 5 6 7
We could now safely
get the wrapped value on the
Try[Iterator[String]] that we assigned to
content, because we know that it must be a
content.get.foreach(println) would result in
Please make sure to enter a valid URL being printed to the console.
Idiomatic error handling in Scala is quite different from the paradigm known from languages like Java or Ruby. The
Try type allows you to encapsulate computations that result in errors in a container and to chain operations on the computed values in a very elegant way. You can transfer what you know from working with collections and with
Option values to how you deal with code that may result in errors – all in a uniform way.
To keep this article at a reasonable length, I haven’t explained all of the methods available on
Try supports the
orElse method. The
recoverWith methods are also worth having a look at, and I encourage you to do so.
In the next part we are going to deal with
Either, an alternative type for representing computations that may result in errors, but with a wider scope of application that goes beyond error handling.