Checked Exceptions
In Java, we have two types of exceptions, checked exceptions and runtime exceptions (I am not including the Error class). The only difference between these two types, is that the checked exception is part of the signature of the method and must be handled by the caller. Handling is done by either catching it, or adding it to the signature as well. Opinions are divided on the usage of them, and in this post I want to discuss these different opinions.
The advantage of using checked exceptions is that you can't forget about them, the compiler tells you you have to handle them, one way or another. This is very helpful, especially as we like to have compiler support and warn us, even prevent us from making mistakes. Yet, many people don't like checked exceptions and prefer to use runtime exceptions for everything. With the addition of lambdas in Java, this voice has only become stronger, as you can not have checked exceptions being thrown in a lambda.
This leads to the reason why most people don't like checked exceptions. In most cases, when you have to deal with a checked exception you either swallow it, or wrap it in a runtime exception. Neither options are any good and should be avoided, as they simply add clutter to the code.
try {
...
} catch (final IOException e) {
// swallow
}
try {
...
} catch (final IOException e) {
throw new RuntimeException(e);
}
The biggest downside of runtime exceptions is however exactly the same as their strong point. The compiler doesn't tell you about them and thus it actually becomes very easy to forget about them. This then leads to another issue, that is that some very specific exception may be thrown up to some level where it doesn't make much sense to handle. For instance, imagine you have a class that is responsible of sending or writing data in a specific format (JSON for example), if something goes wrong with serialising or deserialising the data, you have no other choice but to thrown an exception. There isn't however much you can do with this exception, as it is likely just some bug in your code. Having to deal with a JsonException in your top layer however is rather annoying, moreover it actually exposes an implementation detail that should have been hidden. What if you change from JSON to XML or some other format? You change the exception and thus you have to change the logic in your top layer to handle the exception. This doesn't make any sense, or if you may even forget to handle the exception in your top level, which would result in some generic error handling.
The solution for this is however rather easy. You shouldn't throw exceptions that are that specific. In this case your class shouldn't thrown an JsonException but some more generic SerialisationException, StorageException or ClientException. It may however be difficult to find the right exception to throw, and even the right exception at this level, may not be the exception you want all the way to propagate up. This means that at a higher level you should catch this exception and either handle it correctly because you can, or convert it to a more fitting exception. This is however the other issue I talked about. Since the exception is not part of the signature and the compiler doesn't tell you about it, knowing which exception to catch can be tricky. Changes to the type of exception can also go unnoticed.
A general rule of thumb would be to use runtime exception if it is an exception for which you don't except any actions can be taken (and making sure the type of exception is then generic enough), and use checked exceptions for something that you expect to be handled correctly. An example of the letter could be some connection issue to a third party for which you want to retry a couple of times. Only after a certain amount of retries you want to give up and throw a different (runtime) exception.
In practice however, I currently tend to use runtime exceptions for all of my exceptions. Mostly because I haven't seen many exceptions for which you do actually want to retry something. In many cases, exceptions are simply caused by bugs in your code, and in many other there isn't much you can do about it (third party issues). However, I can imagine that if you deal with a bigger application, with a bigger user base, you do want to plan better for failure, as failure becomes more frequent with more requests. There is however no reason in planning ahead of time, and it is best to tackle issues as they pose themselves.