Harald Kirsch

genug Unfug.

2014-10-27

To check or not to check, what is your Exception

Several years ago, Robert C. Martin declared in Clean Code that the debate is over with regard to the use of checked or unchecked exceptions in Java. I bought into this opinion for some time until it really got in the way recently, where an otherwise nice library to simplify JDBC use catches the SQLException deep within and converts it into a general RuntimeException. Since I wanted to catch the SQLException to perform a rollback, I was forced to dig into the code and follow all possible paths to see which kind of unchecked exceptions may be thrown were to then decide whether its sensible or not to peform the rollback.

While the library declares its own UncheckedSqlException, I was still forced to look through the code to find out. But in case I missed one of the conversions to unchecked, the only safe way is to catch RuntimeException and then figure out wether it is a real runtime exception to bubble up or just an unchecked one that should trigger the rollback.

"What do you mean by real runtime exception?", I hear you ask. Well, the Java documentation puts it this way: Runtime exceptions represent problems that are the result of a programming problem, the canonical example being the NullPointerException. You could as well call it the result of a programmer's mistake. This seems clear-cut enough, doesn't it?

Then lets try this seemingly simple definition on an example: Pattern.copile(regex) throws an unchecked PatternSyntaxException if the string provided does not parse as a regular expression. So lets consider the code

      Pattern varname = Pattern.compile("[A-Za-z][0-9");

which is missing a close bracket in the regular expression. Clearly a programming problem, a programmer's mistake, so the unchecked exception is well deserved.

But now consider reading regular expressions from a configuration file. The programmer has no control over the strings found and whether they are well formed regular expressions or not. For the pattern to not compile now is not a progamming problem, but completely normal behavior, because the programmer has no means to check whether a string will compile other than by trying it. And we certainly do not want an additional method isWellFormedRegex(String s), because checking for well-formedness is as expensive as just trying to compile the string.

This example shows, that under the definition from the Oracle tutorials, for some APIs neither checked nor unchecked exceptions are correct for all use cases. Interestingly, parsing a date with DateFormat.parse() throws a checked exception, while the new LocalDate.parse() again throws an unchecked exception. In particular for date parsing I think it happens much more often on input data than on static strings, so I would prefer a checked exception.

Now what? When even the creator's of the Java core class libraries are unsure, how am I supposed to know what exceptions to use? The string parsing examples show it quite well. When parsing static strings, a parse error is really a programming problem and deserves an unchecked exception. When parsing input data, a parse error, however, is completely normal business. Wait, what did I just say, normal business? Why do the methods then throw an exception at all, when nothing exceptional is going on?

In C there are no exceptions (just core dumps). Methods that want to signal that they cannot return the expected value do so by returning a special value, often -1, and set a global error message. Not that I want to go back there, but the idea to either return a value or a description of what went wrong, in particular when it is normal business that something goes "wrong", can be implemented in Java quite elegantly. Java 8 gave us Optional, but it only provides a value — or not. It does not allow to pass an explanation of why a value is not available, which is what exceptions do. But we can roll our own.

We need something like Optional which does not only hold a value, but can also provide an explanation if no value is available. Since we are so used to exceptions, the explanation could be an exception with the full stack trace. Another alternative could be just a message, but lets go for the exception first. In German I would not mind to call the class Üei, which is short for Überraschungsei = Kinder Surprise, but lets call it ValEx, because it can contain a value or an exception.

    public class ValEx<T,E extends RuntimeException> {
      private final T t;
      private final E e;
      public ValEx(T t) {this.t = t; this.e = null}
      public ValEx(E e) {this.t = null; this.e = e;}
    }

This class can be instantiated with either a value or an exception. Now for the methods. Naturally we must be able to get the value.

    public class ValEx<T,<E extends Exception>> {
      ...
      public T get() throws E {
        if (t==null) throw e;
        return t;
      }
    }

But we also want to be able test whether there is a value available to avoid the exception.

    public class ValEx<T,<E extends Exception>> {
      ...
      public boolean isEmpty() {
        return t==null;
      }
    }

Of course we can easily add more convenience methods like a get(T defaultValue) and a getter for the exception. Now suppose that a parsing method for regular expressions was declared to return a ValEx object like.

      ValEx<Pattern,PatternSyntaxException> compile(String s);

Then we could now call it with a static string like so:

      Pattern p = Pattern.compile("[a-z]+").get();

Since PatternSyntaxException is an unchecked exception, we get what we deserve if we pass a string that is not a regular expression. On the other hand, if we are compiling a property value we got from a file, we would use

      String regex = props.get("pattern");
      ValEx<Pattern,<? extends Exception>> p = Pattern.compile(regex);
      if (p.isEmpty()) {
        // do what is necessary, like logging the wrong pattern
        LOG.warn("ignoring wrong pattern "+pattern, p.getException());
        return;
      }

Whether the explanation when no value is available should always be an exception, is debatable. Creating exceptions with their whole stacktrace is not a lightweight operation (so I heard). Another aspect of this implementation is that even on the fast, normal path of the computation, we always create the ValEx object, which is ready to be garbage collected very soon after.