Handling Kotlin Exceptions with Kategory – A Functional Approach

Roberto Guerra

 11 min read

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

Exception handling in Kotlin is similar to many other programming languages like Java, Ruby, C++, Python, Javascript, etc. This type of exception handling gives the user of the APIs throwing the exceptions little indication at compile time of the exceptions that might be thrown at runtime. Java experimented with checked exceptions in an effort to address this but it didn't go so well.

Case Study

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 okhttp will 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 java.lang.Exception:

loading

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 Handling

Most statically typed functional programming languages provide two abstractions for handling errors and exceptions: Either and Try. For handling exceptions, we generally want to use Try; while Either is 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 Try:

loading

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 Success subclass.

Using functions that return a Try

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. Try provides 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 Try.

Providing defaults with getOrElse

This is used for retreiving the value for the Success subclass. It is null safe because it forces us to provide a default value in the event that the result of the operation was a Failure: loading

Processing errors with fold

fold is very similar to getOrElse but 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 fold take a function that has the Success value as a parameter. loading

Continuing on with recover

recover is similar to fold but only allows us to map/transform the failure case. If the result of a Try is a Success, then the lambda inside the fold is 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 Success case.

We should also note that even though getOrElse and recover look similar, and both allow us to provide a default value for error cases, recover returns another instance of Try while getOrElse does not.

loading

Safeguarding with for comprehensions

If you are familiar with Scala's for comprehensions, Kategory also 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 comprehensions by directly invoking the monad factory on the Try type. We want to use this when we need to do some operations on the Success value in a sequential manner: loading

Inside the binding we don't have to worry about handling Failure. The operations will no-op and binding will return a Failure.

The example above will block until completion, but if we want to make it non-blocking we can use a variant of recover called recoverWith:

loading

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 flatMap:

loading

I personally find the binding example much more readable, but that is just a matter of taste.

Pattern matching with when

Lastly, we can also use Kotlin's default when to extract the value of a Try. For the Success case, the value can be accessed via the value property in the Try instance, and for the Failure case, the value can be accessed via the exception property. One caveat to keep in mind is that if we replace one of these cases with else we won't be able to access the corresponding property because the compiler will not know which sub-class it should be working with. loading

Interesting tidbit, fold is actually implemented internally as when and inlined.

Conclusion

It 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 Kategory brings to Kotlin, we write more reliable code when working with libraries that might throw an exception.

References: