Yet another case against checked exceptions

This subject has been discussed over and over on the web. Since the introduction of checked exceptions in Java, there have been a lot of articles, blog posts, stackoverflow questions about them (you can just type "checked exception" on google and count the pages that discuss if they are a good idea). Should you use checked exceptions? Should you use them? Should you throw them? The answer is not clear. But it's not that foggy either. I think there is a tendency, and here how I understand it:

Don't use checked exceptions, except when you're sure you need one.

Ok, that's not helping. But actually, it is helping if you understand what "When you're sure you need one" means. And if you understand what "use" means in this context. Moreover, put like that, it's clear that the default behavior should be to not use checked exceptions. That's the tl;dr of this article: When in doubt, always use unchecked (or runtime) exceptions.

But as always, you shouldn't believe me just because I say so. In the following, I'll expose my reasoning and hopefully, you'll agree with me.

But What are checked exceptions?

Error handling in the middle ages

Let's start with a bit of history. If you did some programming using a language like C for example, you know how painful it is to do some proper error handling. For example, let's say you want to define a function that returns the square root of a number. You might write something like this :


double sqrt(int n) {
    // Error handling: a negative parameter is invalid.
    if(n < 0) return -1.0;
    
    // Code that computes a sqrt...
    return result;
}

That way, you just have to check the return value of the function. If it's positive, that means everything went well. You can go ahead and use the result. But if the result is negative, that means something went wrong and you have to handle the error.

Ok, that's a good way in a limited language like C. Most C libraries use this technique to do their error handlings. But what if you need a logarithm function?


double log(double d) {
    // Error handling: a negative parameter is invalid.
    if(d < 0) {
      // ??? What to return in that case?
    }
    
    // Code that computes a log...
    return result;
}

The problem arises when there's no invalid value you can return. Every double value is the logarithm of something. You error handling just became harder...

Of course there's always a way : return a int for the error handling and use a pointer parameter to retrieve the logarithm. Or set a global flag to say if everything went well, etc. All these solutions are tedious to use, and they are not consistent with each other. They are not good solutions.

Enter the exceptions.

The better way: Exceptions

The idea behind exceptions is simple: when something bad occurs, there's no reason to continue the code you were executing. In that case, you can "throw an exception" and immediately stop the execution of the current function. The program will go back where the function was called and notify the caller than something went wrong. It's then up to the caller to "catch the exception" and deal with the error. For example, if you wanted to write the logarithm in C++, here's what you might do:


double log(double d) {
    if (d < 0) {
        throw std::exception("Invalid negative parameter for 'log' function");
    }
    
    // Compute the log...
    return log;
}

int main() {
    try {
       double result = log(2.5);
    }
    catch (std::exception e) {
        //error handling...
    }
}

Isn't it better? No need to worry about returning an invalid result. The exception thrown is passed separately to the calling function. And that's completely logical: it's not a result, what should it follow the same path as the result?

The calling function catches the exception and can do whatever it wants with it. And if it doesn't want to catch it, the exception will continue up the call stack until it's caught or until it goes up the main() function and crashes the program.

The Java way : Checked Exception

The Java language introduced a new concept on top of regular exceptions : checked exceptions. To my knowledge, it's the only popular programming language that has them. The principle is that Java exceptions are derived from one of two base classes:

  • Exception : these classes are checked exception, I'll describe them right after this.
  • RuntimeException : these are your regular unchecked exceptions. They behave exactly like C++ exceptions.

If a method might throw a checked exception, it has to declare it like this:


public double log(double d) throws LogarithmException {
    if(d <= 0) throw new LogarithmException("Incorrect parameter");
    
    return log;
}

This doesn't seem like a big deal, but it means that any code that calls this function has to either catch it, or declare it too (because the exception will continue to travel up the call stack, just like any exception would. For example, this:


public double computeSomethingUsingLog(double a, double b, double c) throws LogarithmException {
    result = log(a*b) + c;
    
    return result;
}

This method doesn't know how to handle the case where a*b is negative. So it doesn't catch the exception. But then, it has to declare it. And if no one in the call stack knows how to handle it? You've got it. Every method up to the main() method will have to declare throwing an Exception, or one of them will have to catch it and do nothing with it, just because we don't want the program to crash.

Okay, I got it. Now why is it so bad?

The first thing to understand is that checked exceptions are not intrinsically bad. The issue is that they cause a lot of programmers to write bad code. There are a lot of anti-patterns associated with checked exceptions, and that is a big, big issue.

The exception-bloating anti-pattern

This is the first problem that comes to mind. One that I already briefly mentioned: Declaring exceptions is fine when there's one exception returned by a method, but it's very tedious when there are a lot of them, or when they go far up the stack. This shouldn't happen: a method that can throw 5 or ten different exceptions is a strong code smell. The function probably doesn't respect the single responsibility principle, or maybe it doesn't do enough error handling. But nevertheless, it would be better if the exception declaration didn't come make things worse.

The exception swallowing anti-pattern

This is the main issue. Imagine you are developing a method and one of the method you call declares that it might throw an IOException. The java compiler requires you to catch it or declare it too.

  • you don't want to declare it, because that would just move the problem elsewhere in your code.
  • you don't want to catch it, because you don't know what the heck you'd do with the exception. There's no way you can recover from an IOException!

So, what to do? A lot of programmers will do something like this:


public void exceptionSwallowingMethod() {
    try {
        functionThatThrowsAnException();
    }
    catch (IOException e) {
        // should never happen...
    }
}

That a terrible idea. I know, the function should never throw the exception. What's the point doing something about it? Yes you're right, but what if it does throw the exception ? Nothing will happen. You'll just continue your code, until it crashes because your function didn't do all of its work. Or worse, until it doesn't crash and returns an incorrect result, causing your client to lose 1.000.000 €.

The "//should never happen" comment is declined in a thousand variations. Log something and do nothing else, print the stack trace, etc. This doesn't solve the problem: a method reported an error, and you just silenced it.

How should I use exceptions as an API developer?

As an API developer, exceptions are a way to tell the consumer that something went wrong while the method was doing its job. There are several reasons this can happen:

  • The consumer of the API gave invalid parameters
  • A service used by the method malfunctioned or threw an exception
  • Some persistent data is inconsistent with parameters or values coming from a service used by the method.

These case would be handled differently, and a popular advice about exception is the following:

Throw a checked exception when you expect the consumer of your API to be able to handle the error, throw an unchecked exception otherwise.

Yeah right. Like you can always know what the consumer will or will not be able to do. This is a good idea, but I find it hard to implement in practice. Sometimes you think the client will be able to handle something, and you throw an exception that might as well be called OverzealousCheckedException. The consumer has no idea what to do with it and he ends up falling in one of the anti-patterns I mentioned earlier. You might as well throw an unchecked exception. At least It won't pollute every method signature or be swallowed.

Another often used argument is that declaring an exception is a way to further document your API. For example, if I had a method like this:


public File openClientFile(Client c) throws FileNotFoundException {
    ...
}

the declaration of my method is pretty clear. It takes a client as an argument, and returns a file associated with the argument. But beware! you have to consider the possibility that the client doesn't have a file for example. This is a good example of a self-documenting code.

Just so I don't get accused of being partial, let me say that this is a good use of checked exception. in a method openClientFile(), yes, I expect the consumer to be able to do something if the client doesn't have a file. So it's fine to use a checked exception, and it's nice to document the method with the throws keyword.

But let me suggest another way to do the same thing :


/**
 * Find the file associated with a client and open it.
 * @param c the client
 * @return the file
 * @throws RuntimeFileNotFoundException if the client file was not found
 */
public File openClientFile(Client c) {
    ...
}

What's wrong with that? I documented the exception just fine! Even better I'd say. And you have to admit: the javadoc would have been a good idea in the first case too.

You probably noticed that I don't like the argument of the "self documenting code". Come on guys, use javadoc, it's better than any self documenting code. Self-documenting code is for code not for the API documentation.

And even if you don't agree : There's nothing that forbids you to declare your unchecked exceptions. By all means, if you like this way of documenting your code, do it. But at least, if it's an unchecked exception, you're not polluting the consumer.

Why use exceptions as an API consumer?

Or, said differently, when and how should you catch exceptions?

Obviously, as I said earlier, don't catch them to swallow them or just log something. Catch them when you want to handle the error and recover. Oh, and don't do that either:


try {
   somethingThatThrowsExceptions();
catch(IOException e) {
  //TODO some error handling
}

Let's be honest for a second. You're never coming back to that todo. And if you had the luxury to leave that todo here and continue your code, it means one of two things:

  • Either you don't expect the exception to be thrown at all;
  • Or you didn't know how to handle it anyway.

In that case, then don't catch it.

"But I don't want to declare it in my method signature! The exception has nothing to do with my code."

And this is the heart of the problem. We're coming back to the exception bloating. In that case, it probably means that the API you're using declared an OverzealousCheckedException. My suggestion, in that case, is this simple technique:


try {
    somethingThatThrowsExceptions();
catch(IOException e) {
    throw new RuntimeException(e);
}

There are several advantages to this technique.

  • No more exception bloating. You don't have to declare any exception. You can declare only the exceptions you want
  • No exception swallowing. If there's an error, it will either crash the application, meaning there's a bug (and hopefully it will be detected during testing) or be caught and handled by someone.
  • Runtime exceptions that are caught are more likely to be handled properly. If you're a lazy developer, you're not going to bother catching a runtime exception if it's not to recover from the error.
  • It's much better than a todo. When your application crashes, you'll come back to that code snippet and fix it.

Summary, and what you should do (Read this if you find my article too long)

As an API Developper

You should only throw unchecked exceptions. Checked exceptions are for the case when you're absolutely sure the consumer has to catch the exception. The thing is, in my opinion, there's no such case. If you're not sure, throw an unchecked exception and document it in your javadoc. That way, if the client needs to handle it, he will know it's there and can be thrown. If he doesn't need or doesn't know how, he won't bother and won't be bothered. If you think it shouldn't happen except if there's a bug somewhere, throw an unchecked exception and don't say anything. If it happens, the consumer will see it at some point and take necessary measures.

As an API consumer

Catch the exceptions when you will handle it and/or recover from the error. And by handle it, I don't mean log it somewhere. If you don't want to handle it, or if you don't know how, don't catch it. If it's a checked exception, catch it and throw it back wrapped in a basic RuntimeException (see the code snippet earlier).

Conclusion

There, I've done it. I've written another article on a subject that has way too many articles about it. But hopefully it will be a different approach of the subject that what you have read elsewhere. And in any case, repetition is good to assimilate something.

If you don't agree with me, of if you do agree, don't hesitate to leave your opinion below, I'd love to hear from people that read me.

This article is my 5th oldest. It is 787 words long