Error handling

Exceptions

At the outset of the project, we decided to use undeclared exceptions throughout our code to avoid to avoid cluttering method definitions with exceptions that are merely passing through, and to have more flexibility in what exceptions can be thrown in subclasses and interface implementations. Before you argue this decision, please read the arguments for and against. Notice that the fact that an exception is unchecked does not mean that you don't need to document its usage in !JavaDoc for your methods, and you can still add it to the throws clause.

At any point where exceptions enter our code, we catch them and either handle them immediately or throw one of our exceptions instead, with the caught exception as the cause. All our exceptions inherit from dk.netarkivet.common.exceptions.NetarkivetException, and we try to keep the number of exceptions at a minimum. At the moment, the following exceptions exist:

  • dk.netarkivet.common.exceptions.!PermissionDenied - used when user rights are not sufficient to perform an operation, or authentication has failed.
  • dk.netarkivet.common.exceptions.UnknownID - used when trying to look up an item that does not exist..
  • dk.netarkivet.common.exceptions.IOFailure - used for a plethora of unpredictable file-system or network failures, when no better cause (like !PermissionDenied) can be ascertained.
  • dk.netarkivet.common.exceptions.!ForwardedToErrorPage - used solely in JSP page support code to abort operations after forwarding to an error page. This should be caught in the JSP page and processing of the JSP stopped.
  • dk.netarkivet.common.exceptions.!ArgumentNotValid - used in all public methods for checking basic validity of arguments. Covers and provides methods for checking errors like passing null references or empty strings. Should not be used to indicate things like missing files.
  • dk.netarkivet.common.exceptions.!IllegalState - used when something can be in one of several states, and an operation is performed that is not appropriate for the current state.
  • dk.netarkivet.common.exceptions.!NotImplementedException - used as a placeholder in methods that are not implemented, or in a few system-specific places for instance trying to get the number of bytes free on a disk when running on a system that doesn't have that function implemented.

One standard example of how to catch outside exceptions and handle resource freeing is:

    InputStream in;
    try {
        try {
            in new FileInputStream(file);
            in.readAll(...);
        } finally {
            if (in !h1. null) {
                in.close();
            }
        }
    } catch (IOException e) {
        throw new IOFailure("Failed to read file '" + file + "'", e);
    }

Notice how the error message contains the file name in quotes (makes it easier to understand empty file error), and how the IOFailure gets the original exception passed in – it is very important to never let the original exception vanish.

When it comes to handling our internal exceptions, the general rule is: Avoid catching exceptions unless you need to catch it. Also expressed as "Never catch an exception that you do not know how to handle" (with apologies to H. P. Lovecraft).

You need to catch an internal exception if:

  • Resources must be released that cannot be properly releases with finally. Pay special attention to constructors.
  • Your code can fix the problem and try again
  • Your code must try an alternative execution strategy
  • Your are implementing a toplevel method like main()