Know Your Catcher Before Throwing
For Whom are Exceptions anyways?
I’m currently in the progress of creating a REST API with Spring and a few other libraries. Part of that is, of course, to properly handle all exceptions that might occur and present them properly to the API user.
And while trying to exhaust all of the possible exceptions, I quickly realised: Those exceptions weren’t meant for me!
The Two Types of Exceptions
For the purposes of this post, I’ll separate exceptions in two different kinds, or rather two different use cases, and talk about them separately:
- Internal exceptions that only you care about and
- external exceptions meant for the user of your API.
By internal exceptions I mean exceptions that will never leave the code that you’re responsible for. An easy way to recognize them is if you wouldn’t have problems introducing breaking changes to them.
External exceptions on the other hand are exceptions that you won’t (necessarily) catch. The users don’t have to be the end users of your product, but could be other teams in the same company.
Design Exceptions for Yourself
The first type of exception is the less hard one to design. But I would always question why you’re creating them in the first place.
Creating exceptions that are caught again in the same module essentially are only used for short cuts — so flow control. You could always refactor it to not use exceptions and could offer the same interface to the outside.
I’m not saying that they shouldn’t ever exist, but if you decide to create an internal exception make sure you can justify why.
One case where you cannot avoid having internal exceptions is when you’re using a library that throws them. In that case you’re the consumer of an external exception, but you still need to treat it like an internal one.
The reason for that is that you should always wrap exceptions from internal libraries. Otherwise you expose internal information and (maybe even worse) make yourself completely dependent on that library.
Design Exceptions for Others
This is the harder part — and the one that some libraries we use for our REST API didn’t get right.
When passing exceptions to your users, you’re telling them that something went wrong. You should also tell them what went wrong in as much detail as possible. It’s essentially just another possible return value of your API.
Provide your user with meta information
On the top-most level a user of your API needs to know:
- Is it my fault that this exception happened? Did I mess up the input or some setup?
- If I do the same thing again, will it have the same effect or could it work this time?
- Should I notify anyone when this happens or is it expected to occur sometimes?
You might notice that for REST interfaces you can figure out all of these questions by looking at the return status code of a HTTP call. In code APIs, this is usually signified by the class of the exception.
This meta-information is necessary for the user of the API to know how to react properly: Should we bubble it up to the end user, log it, present the end user with a generic message, try again or any combination of them.
Information should be on the same clearance level
One parsing library that we used threw an exception with a pretty nice error message that you could present to a user directly. Think something like “Property ‘id’ may not be empty”. But after playing around with the library a bit more, it turns out that there is a special case in which it does not have such a nice error message and instead just wraps another exception and inherits its message. Think something like “Cannot cast java.util.ArrayList to java.util.Set”.
Normally you should never return an exception message from a library directly anyways. It would just mean that your message might also suddenly change when you update the library and that you cannot know all possible phrases. But sometimes it’s tempting (especially for less experienced programmers — to which I count myself).
In the above case, they could either try to get a better message even in that special case or throw a different exception. Or, even better, don’t rely on the message as much, but provide the information separately (see the next point).
To be fair, this is less a fault of the library, but more so by the user. Still, a good API will not allow its users to make mistakes (or at least make it as hard as possible).
Information must be deconstructable
This is probably the most important point: Don’t assume that your user wants the information in exactly the format that you provide.
Not only does it make it more explicit what information is included, but the user can also select only parts of the information and decide how to present that. This leaves you free to add more information later, that might be more sensitive.
Another obvious use case for this are translations into other languages. A parsing exception should provide me with the property name and the an error selected from a well-defined, finite set of possible errors. Then I can choose myself how to word it and could even translate it into different languages.
Another common use case is if multiple libraries are used for things that a user would assume are the same thing. For instance parsing two different JSONs that are both adhering to well-known schemas and therefore have their own dedicated parsers.
One library might return “Property ‘id’ may not be empty” and the other “Invalid ID: String must not be empty”. Both sentences say the same thing, but if a user is presented with both of them at the same time, it’s sure to raise an eye brow.
As with everything we create, it’s important to keep the user in mind. It doesn’t matter whether that’s an end user of our product or fellow programmers. Provide them with the appropriate tools to do their jobs.
When creating exceptions, it’s easy to just add a message and forget the rest. After all, it’s the standard for a vast majority of standard exceptions in many languages.
But just like normal “success” value returns, exceptions also have to be designed with care.