wiki:hauma/blog/UndeclaredCheckedExceptions

Throwing Undeclared Checked Exceptions

A checked exception in Java is an exception that must either be caught or declared to be thrown. As the name suggests, the compiler ensures that these requirements are fullfilled or otherwise reports a programm error. This page shows that it is possible to throw undeclared checked exceptions in "pure Java". Checked exceptions are debatable, but the possibility of throwing them undeclared, finally renders their right to existence void and suggest that the specification is broken somewhere.

Checked Exceptions

A good example for a checked exception (and possibly the most hated one in concurrent programming) is the InterruptedException that could be thrown e.g. by the method Object.wait(). Since InterruptedException is checked, the following code is wrong:

// Method with undeclared checked exception (wrong code).
synchronized void foo() {
   while (! conditionFullfilled()) wait();

   // do some work
}

When compiling a class with the method foo() shown above, the compiler reports the following error:

unreported exception java.lang.InterruptedException; must be caught
or declared to be thrown:

   while (! conditionFullfilled()) wait();
                                   ^

Dealing with checked exceptions

To get rid of this error, one must either declare that method foo() throws InterruptedException, or catch and handle it locally. Either of both options has its drawbacks. Locally handling (not ignoring or just printing out) the exception might not be possible, but when declaring it to be thrown, all methods that call foo() have the same problem of deciding what to do with the unloved InterruptedException. The most common mistake is to silently suppress this anoying exception by wrapping calls to e.g. wait() with a handler for InterruptedException that simply ignores it:

// Anti-pattern: Ignoring checked exceptions
try {
   wait();
} catch (InterruptedException ex) {}

This of cause makes the current thread uninterrupible and renders the Thread.interrupt() API pretty useless. The encuraged action to be taken, when calling methods that throw checked exceptions, which cannot be handled locally, is to also declare the current method to throw these exceptions as in the following example:

// Method re-declaring an unhandled checked exception (encuraged usage)
synchronized void foo() throws InterruptedException {
   while (! conditionFullfilled()) wait();

   // do some work
}

If a program deals with multiple checked exceptions, code quickly gets bloated with anoying exception declarations. It seems that also the inventors of Java are fed up with declaring checked exceptions. And as always, they invented a nice (or broken) trick with which it possible (only for them of cause) to throw checked exeptions without the need for declaring them. This scandalous feature is implemented within a native method (as always) within the non-standard class sun.misc.Unsafe.

Nobody would care, if this anti-feature of throwing undeclared checked exceptions was only a detail of the leading Java vendor's JVM implementation. But this problem is not only application visible but also part of the core Java specification and therefore a requirement for all JVM implementations.

Example throwing an undeclared checked exception

The class java.lang.Class declares a public method newInstance() that allows creating an object reflectively. Two exceptions are declared by newInstance() (from the API documentation):

  • IllegalAccessException: if the class or its nullary constructor is not accessible.
  • InstantiationException: if this Class represents an abstract class, an interface, an array class, a primitive type, or void; or if the class has no nullary constructor; or if the instantiation fails for some other reason.

This is different from the method java.lang.reflect.Constructor.newInstance(), which serves the same purpose, but declares the additional exception:

  • InvocationTargetException: if the underlying constructor throws an exception.

What will happen, if the constructor of a class throws a (checked) exception and an instance of this class is created reflectively using Class.newInstance()?

Assume the following class A with a public no-argument constructor declaring and throwing a checked exception:

class A {
    public A() throws InterruptedException {
        throw new InterruptedException();
    }
}

We can (try to) create an instance of A reflectively using the following factory method:

    public static A newInstance() {
        try {
            return (A) A.class.newInstance();
        } catch (InstantiationException ex) {
            throw new RuntimeException(ex);
        } catch (IllegalAccessException ex) {
            throw new RuntimeException(ex);
        }
    }

In the above code, all checked exceptions declared by Class.newInstance() are wraped into non-checked RuntimeExceptions. Therefore, the factory method does not need to declare any exceptions. Of cause, we do not expect its invocation to terminate silently, because instances of A are basically unconstructable since A's constructor always throws an InterruptedException.

Consequences

We try anyway to invoke the factory shown above from within the program's main method to see which exception is actually thrown:

    public static void main(String[] args) {
        newInstance();
    }

As expected, the invocation of this program terminates with an exception. As not expected (because no checked exceptions are being declared by main()), this exception is a checked exception (the java.lang.InterruptedException that is thrown from the constructor of class A):

> java -cp . A
Exception in thread "main" java.lang.InterruptedException
        at A.<init>()
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance()
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance()
        at java.lang.reflect.Constructor.newInstance()
        at java.lang.Class.newInstance0()
        at java.lang.Class.newInstance()
        at A.newInstance()
        at A.main()

If you do not yet feel the calamity of this situation, you may try to catch this InterruptedException in main():

    public static void main(String[] args) {
        try {
            newInstance();
        } catch (InterruptedException ex) {
            System.out.println("No tragedy, only an Interrupt.");
        }
    }

If you now try to re-compile your program, you get insulted by the Java compiler with the following obscene error message:

A.java: exception java.lang.InterruptedException is never thrown in body 
           of corresponding try statement

        } catch (InterruptedException ex) {
          ^
1 error

The assumption behind this error is the fact that Java guarantees checked exception to be thrown only where this is apparent from the context (where such exception is declared). Obviously, this guarantee is void. The second thing is that the Java specification disallows dead code (with exceptions not discussed here). Therefore, the compiler must reject the main() method above, because the exception handler for InterruptedException seems to be unreacheable.

As a practioneer, you might modify the catch block above and catch Exception instead of InterruptedException. This again makes the compiler happy, but of cause changes the semantics of your program: Instead only one special form of exception being caught, almost all exceptions are now handled by your catch block. And the bad new is: you can't get rid of them. Assume the following workaround:

    public static void main(String[] args) {
        try {
            newInstance();
        } catch (Exception ex) {
            if (ex instanceof InterruptedException) {
                System.out.println("No tragedy, only an Interrupt.");
            } else if (ex instanceof RuntimeException) {
                throw (RuntimeException) ex;
            } else {
                // What to do here???
            }
        }
    }

In the code above, Exception (the base class of InterruptedException) is caught and dynamically checked for being accidentally an InterruptedException. To get the required semantics, the code must re-throw the exception in all other cases. If the caught exception is a RuntimeException (the base class of all unchecked exceptions), it is possible to downcast and re-throw the exception directly. In all other cases, we cannot re-throw the exception without declaring such exception being thrown (since Exception is checked). Ah, of cause, and because we as being ordinary application programmers are not allowed to use the unsafe features in sun.misc.Unsafe.

You may still say, this is not a problem at all, because we can be sure not to walk into the else branch of the if expression, because ex simply cannot be another checked exception, and we simply throw an AssertionError here. But what if there is just one another checked exception being thrown in an unsafe manner by another broken Java API method?

I discovered this issue when reading the Core Java Technologies Tech Tips (April 19, 2005). The second part of the tech tips deal with classes from the java.util.concurrent.atomic package that are new to Java 5. The tip encurages the usage of classes like AtomicInteger to avoid costly synchronization, when accessing single primitive values from different threads in parallel. When reading this, I got curious how atomic access (like test and set) could be implemented in Java without synchronization. Maybe, I'll deal with that on another page. For now it is only relevant that this mysterious feature of atomic access is delegated to an opaque native method in the class sun.misc.Unsafe, which has multiple really dangerous methods.

Digging somewhat further, I discovered Don Schwarz's blog on Avoiding Checked Exceptions. There he explains, how one might get access to these broken features in sun.misc.Unsafe and in what circumstances this could be benecifial. I strongly disagree with him and think, at least the application visibility of such unsafe effects is a dangerous bug in Java.

Disabled comments, too much spam.

Last modified 10 years ago Last modified on Oct 14, 2010 4:07:06 PM