Handling Kotlin Exceptions with Kategory – A Functional Approach
Kotlin has excited us at Spantree since we've first heard about it. As advocates for alternative languages on the JVM, we love the benefits it's brought to client and internal projects. The biggest benefit Kotlin has provided in the code I've written with it is the way it can handle exceptions and errors.
Working with libraries and APIs that throw exceptions can lead to unexpected behavior at runtime. In my experience, this mostly happens because I normally get no indication about any exceptions that I need to handle to make the code resilient when using the API. The specific exceptions that should be handled often manifest themselves at runtime when the application starts crashing. Although we can use the traditional Java-style exception handling in Kotlin, we can also use some functional programming concepts to provide APIs that give the users affordances at compile time about the possible things that might fail at runtime. We will illustrate how to use a Kotlin library called Kategory to handle Java libraries that throw exceptions and how to create our own APIs to be more type safe.
Limitations of traditional exception handling
Lets look at a typical example of using an http library to access a remote API to fetch some
data. We'll use
okhttp but please don't take this as a hit on the library. It is probably my
favorite http library for Java. All Java libraries that throw exceptions will behave in
the manner that we will illustrate.
A first pass at creating a function that fetches data from a remote API might look like the following:loading
That function will happily compile and we might even be bold enough to put it in production
(I've deployed code to production like that many times). And it might possibly work for days, months
or years without an issue. But it might also start failing within hours. We can't tell, and the code
we wrote gives us no indication that something might fail. Furthermore,
okhttp's type signature for
making an http call does not tell us what exceptions it might throw if something might go wrong.
We can draw on past experiences of having written services that interface with third party systems
via a network call to know that network issues can occur that will disrupt our services. To be able
to resiliently handle these outages we need to handle the possible exceptions that
throw when something goes wrong. Unfortunately, most of the times we don't know what those
exceptions are until something goes wrong in production or if we read the source code for the
third party library we are using. But reading the code for every third party library
that our application depends on is not practical. Embarrasingly, one of the things I've done
when the exceptions that might be thrown are unknown, I use the catch-all
While that code won't crash in production, it will fail to alert us of any issues that might be
happening that we should be dealing with. It also does not give the parts of our application
that are using
scores an opportunity to either inform the users in the front end that
something went wrong (e.g. MLB is down at the moment; We are experiencing network issues) or
try another service that might be a backup for times when the main service is down.
We can propagate the exception up the stack and that can give the parts of our business application an opportunity to handle the exceptions however they see fit:loading
And we are back to the issues we mentioned earlier: the type signature gives us no indication
of what exceptions might be thrown, and every client of
scores needs to know and also throw
the Exception so that calls from higher up the stack can handle it.
Functional Exception HandlingMost statically typed functional programming languages provide two abstractions for handling errors and exceptions:
Try. For handling exceptions, we generally want to use
Eitheris for business logic errors.
We will be using
kategory to illustrate how
Try can be used to provide a more ergonomic API
for operations that might throw exceptions.
If we know that a library that we are using might throw an exception,
Try will take care
of catching that exception and returning it to the caller. We don't need to know the specfic
exception we are only concerned with it at the end of our stack when we want to do something
with the result of our operation. Refactoring
scores to use
For clients of
scores, they can know right away from reading the type signature that it is an
operation that can throw an exception, and that they need to handle it in their code. The compiler
will also complain if clients of
scores neglect to handle the failure cases. This usage of
Try is helpful when we are interacting with Java (or Kotlin) code that throws exception but
we want to provide a more functional interface for the rest of our application to use.
Try internally represents the failure in its
Failure subclass and the result of a successful
operation in its
Using functions that return a
We've seen how to write a function that might throw an exception and how to use typed abstractions
to make handling these exceptions more explicit. But we have not shown how we use these functions.
Tryprovides a number of helpful functions. It's important to note that while many of these look like functions in
kotlin.collections, these are all provided by Kategory's
Providing defaults with getOrElseThis is used for retreiving the value for the
Successsubclass. It is null safe because it forces us to provide a default value in the event that the result of the operation was a
Processing errors with fold
foldis very similar to
getOrElsebut it allows us to transform the successful value. We provide the value for the failure case, but we also have access to the exception and we can use it to determine the result value. The second argument to
foldtake a function that has the Success value as a parameter. loading
Continuing on with recover
recoveris similar to
foldbut only allows us to map/transform the failure case. If the result of a
Success, then the lambda inside the
foldis ignored. This is useful when we want to return a different value that depends on the exception that is thrown, but we do not want to transform the
We should also note that even though
recover look similar, and both allow us to provide
a default value for error cases,
recover returns another instance of
getOrElse does not.
Safeguarding with for comprehensionsIf you are familiar with Scala's
Kategoryalso implements them. However, the naming is a bit unintuitive for beginners. You have to know what you are looking for in order to find it. We can use
for comprehensionsby directly invoking the
monadfactory on the
Trytype. We want to use this when we need to do some operations on the
Successvalue in a sequential manner: loading
binding we don't have to worry about handling
Failure. The operations will no-op and
The example above will block until completion, but if we want to make it non-blocking we can use a variant of
We can think of
recoverWith as a
flatMap but for the error cases. This example also introduces two new functions.
The first one is
Try.pure, this is just a constructor that is needed to satisfy some algebraic rules (that is beyond the scope of this blog).
And we can call
bind on the result of a
recoverWith (or on any
Try for that matter), which will give you the result, in this case a list of games.
To appreciate what
binding offers, we can write this using
I personally find the
binding example much more readable, but that is just a matter of taste.
Pattern matching with
Lastly, we can also use Kotlin's default
whento extract the value of a
Try. For the
Successcase, the value can be accessed via the
valueproperty in the
Tryinstance, and for the
Failurecase, the value can be accessed via the
exceptionproperty. One caveat to keep in mind is that if we replace one of these cases with
elsewe won't be able to access the corresponding property because the compiler will not know which sub-class it should be working with. loading
fold is actually implemented internally as
ConclusionIt is easy to overlook exception handling when we are working with an operation that might throw an exception because we might not know what those exceptions might be, or we are not forced to think about it from the start. By using concepts from functional programming that
Kotlin, we write more reliable code when working with libraries that might throw an exception.
- Thanks to Paco Estevez, core contributor to the Kategory project, for explaining
Tryand providing the non-blocking example using
bindingin the Kotlin slack.