About exceptions, here's a few rules-of-thumb off the top of my (holiday-relaxed) head:

There are three types of Exceptions that we should throw:

1. "real" exceptions, such as IOException etc., which do *not* extend RuntimeException, should be thrown wherever we can expect something to go wrong. For example, a file we need is not there, a server cannot be contacted, a file does not have the expected content. These should be marked in the method signature, and documented in the Javadoc. Example:

    public void load(String filename) throws IOException
    {
        ...
        File f = new File(filename);
        if (!f.exists()) throw new FileNotFoundException("File '" + filename + "' does not exist");
        ...
    }

(remember that FileNotFoundException is an IOException)

In this category, I can think of:

    IOException (general-purpose file stuff)
    FileNotFoundException
    SynthesisException (a MARY subclass of Exception, indicating that at run-time, synthesis failed for some reason)
    OurOwnException (a self-made subclass of Exception, with a specific meaning)
    Exception (if we are too lazy to define our own exception)

Also, we should not hesitate to "let through" other exceptions from third-party code we use, i.e. it is often a good idea *not* to catch such exceptions. Examples include:

    DOMException thrown by XML DOM handling code;
    SocketException thrown by the Socket handling code in client-server communication
    ... and many more.

If we prefer to catch such exceptions, we must make sure to include the original exception as a *cause* when we throw a new exception. Otherwise, it becomes impossible to track down the source of the problem. Some Exception classes offer a constructor which takes the cause as a second argument:

    try {
    ...
    } catch (IOException ioe) {
       throw new Exception("Problem dumping carts", ioe);
    }

Other Exception classes don't offer such a constructor (at least in Java 1.4). In such cases, we can still add the cause, e.g.:

    try {
    ...
    } catch (IOException ioe) {
       IOException newIOE = new IOException("Problem dumping carts");
       newIOE.initCause(ioe);
       throw newIOE;
    }

2. "safety check" exceptions, for the unexpected but possible case that a *public* method gets unacceptable arguments or similar. Here we throw subclasses of RuntimeException, which do not *need* to be declared; whether or not we still want to document them in the Javadoc is a case-by-case decision. The main examples are:

    IllegalArgumentException (if arguments are unexpected)
    NullPointerException (we should normally throw this only if an *argument* is null which should not be null)

3. And then there are *assertions*. This is for testing the unexpected case that some code *within* a method, or the arguments of a *private* or *protected* method, is not as is required. Assertion testing can be turned on or off on the command line ("java -ea" = "enable assertions"), so in trusted run-time code, assertions can be deactivated so they do not waste any time.

We should make heavy use of assertions; the rule-of-thumb is that one should write an assertion wherever one would write a System.out.println() otherwise. This may be too strict a rule, but it gives the general idea. Example:

    String names[];
    ....
    assert names != null : "Names array is not initialised";
    for (int i=0; i<names.length; i++) {
        assert names[i] != null : "Name " + i + " is null";
        assert names[i].length > 0 : "Name " + i " is empty";
        ....
    }

If an assertion fails, java will throw an "AssertionError" which we should *never* try to catch explicitly. If an assertion fails, something is *wrong* in the program, so processing should fail. An exception to this rule is the MARY server request handler: even if one request fails, the server should continue to run.

For assertions, I have discovered the possibility to add an error message after the colon only recently; so most of the assert statements in the MARY code unfortunately do not have an error message.

4. One thing we should normally *never* throw are Errors. These really are reserved to java-internal malfunctioning, and should normally lead to program termination.