Why is there no common base exception class in Java?

In Ice for C++ (and other language mappings), all Ice exceptions derive from a common base class. For example, in C++, we have Ice::Exception at the root, with derived classes Ice::LocalException and Ice::UserException. In turn, all the Ice run-time exceptions derive from LocalException, and all the Ice user exceptions derive from UserException.

The advantage of having a common base class for user- and run-time exceptions is that you can catch all Ice exceptions with a single exception handler:

C++
try
{
    someProxy->someOp();
}
catch(const Ice::Exception& ex)
{
    cerr << ex;
}

In Java, we also have Ice.LocalException derived from Ice.Exception, but Ice.UserException does not derive from Ice.Exception. If you want to catch all Ice exceptions, you must write two separate exception handlers:

Java
try
{
    someProxy.someOp();
}
catch(Ice.UserException ex)
{
    System.out.write(ex);
}
catch(Ice.LocalException ex)
{
    System.out.write(ex);
}

So, why this difference? The reason is Java's checked exception model. (People either hate or love this model — Kevlin Henney (among many others) provides an interesting discussion of its trade-offs.)

Java distinguishes between two kinds of exceptions, checked exceptions and unchecked ones. For checked exceptions, the language, at compile time, enforces that a method must declare all checked exceptions that it can possibly throw in a separate throws clause. This includes any exceptions that (recursively) might be thrown by any called methods. On the other hand, for unchecked exceptions (which are exceptions that derive from java.lang.RuntimeException or java.lang.Error), the language does not require you to list them explicitly in a throws clause — any method can throw an unchecked exception at any time.

Unchecked exceptions were added to the language out of necessity. For example, imagine the consequences of NullPointerException being a checked exception: either every method would need a throws clause for this exception, or the body of every method would have to catch and swallow this exception (or translate it to some other exception that can be thrown). Clearly, this would be quite intrusive and messy.

Ice follows the Java philosophy: Ice run-time exceptions are unchecked exceptions and Ice user exceptions are checked exceptions. This allows you to write code without eternally having to write throws clauses for Ice run-time exceptions, while still enforcing that your methods correctly deal with user exceptions. The down-side of this approach that you need two exception handlers if you want to catch both Ice user- and run-time exceptions. Note that you can catch all Ice exceptions with a single exception handler:

Java
try
{
    someProxy.someOp();
}
catch(java.lang.Throwable ex)
{
    // Catches too much...
}

Sure enough, this catches all Ice user- and run-time exceptions, but the glitch is that it catches everything else as well, including exceptions that have nothing to do with Ice. As a work-around, you could add further processing in the handler to determine whether the exception is not an Ice exception and, if so, rethrow it:

Java
static void
throwIfNonIceException(java.lang.Throwable ex)
{
    if(!(ex instanceof Ice.LocalException) &&
       !(ex instanceof Ice.UserException))
    {
        throw ex;
    }

    try
    {
        someProxy.someOp();
    }
    catch(java.lang.Throwable ex)
    {
        throwIfNonIceException(ex);
        // Handle Ice exception...
    }
}

However, most people would agree that this rather obscures the issue; it is clearer and simpler to write two separate exception handlers in the few places in the code where you need to catch both Ice user- and run-time exceptions.