Improved Exception Handling for Java

AUTHOR(S):

Neal M Gafter

OVERVIEW

FEATURE SUMMARY:

MAJOR ADVANTAGE:

MAJOR BENEFIT:

Greatly simplifies writing and maintaining code where intercepting or processing exceptions is common.

MAJOR DISADVANTAGE:

One-time implementation cost for adding the features to the compiler.  Longer language specification in describing the behavior.

ALTERNATIVES:

EXAMPLES

SIMPLE EXAMPLE:

try {

    doWork(file);

} catch (final IOException|SQLException ex) {

    logger.log(ex);

    throw ex;

}

ADVANCED EXAMPLE:

Catching Multiple Exception Types

Sometimes you need to handle one of several exception types with the same code:

 

    try {

        return klass.newInstance();

    } catch (InstantiationException e) {

        throw new AssertionError(e);

    } catch (IllegalAccessException e) {

        throw new AssertionError(e);

    }

One alternative is to find a common supertype of these exceptions and catch just that type:

 

    // Broken - catches exceptions that should be allowed to propagate! 

    try {

        return klass.newInstance();

    } catch (Exception e) {

        throw new AssertionError(e);

    }

 

Unfortunately, that can catch more exceptions than you intend.  In this example, it wraps all unchecked exceptions in AssertionError.  This proposal makes it possible to catch two or more exception types in a single catch clause:

 

    try {

        return klass.newInstance();

    } catch (final InstantiationException | IllegalAccessException e) {

        throw new AssertionError(e);

    }

 

Improved Checking for Rethrown Exceptions

Occasionally you need to intercept all exceptions, do something with them, and allow them to propogate. Doing this today is awkward. One way is to catch Throwable. But how do you rethrow it, without declaring Throwable in the current method and everything above it on the call stack?

 

    try {

        doable.doIt();  // Specified to throw several different exceptions

    } catch (Throwable ex) {

        logger.log(ex);

        throw ex;  // Won't compile unless method is specified to throw Throwable!

    }

 

Because this code throws an expression whose dynamic type is Throwable, the compiler currently thinks that the throw statement can throw any checked exception type.  Java Puzzlers describes a couple of ways of rethrowing an exception such that the compiler's exception checking is circumvented. Some folks would resort to secret sun.Misc APIs.  Some programmers wrap the exception prior to rethrowing it:

 

    try { 

        doable.doIt();

    } catch (Throwable ex) {

        logger.log(ex);

        throw new WrappedException(ex);  // Obscures exception type!

    }

 

Unfortunately, this approach wraps all checked as well as unchecked exceptions.  We really want the rethrown exception to be treated as if this try-finally block can only throw checked exceptions that are thrown in the try block.

If a caught exception is declared final, it is safe to rethrow any caught exception:

    try {

        doable.doIt();

    } catch (final Throwable ex) {

        logger.log(ex);

        throw ex;

    }

Because the catch parameter is final, it can only hold exceptions that are thrown from the try block. This proposal enables exception checking to take advantage of that fact: a rethrown final catch parameter is treated as if it throws only exceptions that occur in the try block.

DETAILS

SPECIFICATION:

The grammar of Java is extended to allow a series of exception types, separated by the "OR" operator symbol, to be used in a catch clause:

CatchClause:

 catch ( CatchFormalParameter ) Block

CatchFormalParameter:

 VariableModifiers CatchType VariableDeclaratorId

CatchType:

 DisjunctionType

DisjunctionType:

 Type

 Type | DisjunctionType

The type system is affected as follows: For the purpose of type checking, a catch parameter declared with a disjunction has type lub(t1, t2, ...) [JLS3 15.12.2.5].  For the purpose of exception checking [JLS3 11.2], a throw statement [JLS3 11.2.2] that rethrows a final catch parameter is treated as throwing precisely those exception types that

To avoid the need to add support for general disjunctive types, but leaving open the possibility of a future extension along these lines, a catch parameter whose type has more than one disjunct is required to be declared final.

COMPILATION:

A catch clause is currently compiled (before this change) to an entry in an exception table that specifies the type of the exception and the address of the code for the catch body. To generate code for this new construct, the compiler would generate an entry in the exception table for each type in the exception parameter's list of types.

TESTING:

The feature can be tested by compiling and running programs that exercise the feature.

LIBRARY SUPPORT:

No.

REFLECTIVE APIS:

No reflective API changes are required.

OTHER CHANGES:

It would be desireable, at the same time that this change is made, to update the non-public Tree API that can be used with APT to express the syntax extension.

MIGRATION:

None required.  However, it would be easy to detect a series of otherwise identical catch clauses for different types and collapse them to a single catch clause.

COMPATIBILITY

BREAKING CHANGES:

Joe Darcy observes that the following program compiles before this change, but not after:

try {

  throw new DaughterOfFoo();

} catch (final Foo exception) {

  try {

     throw exception; // used to throw Foo, now throws DaughterOfFoo

  } catch (SonOfFoo anotherException) { // Reachable?

  }

}

However

Because the point of the rethrow proposal is to improve the precision of exception checking, and Java is sensitive to catch clauses that it determines to be unreachable, the most straightforward way to make this change fully compatible is to simply remove Java's requirement that catch clauses be reachable.  The requirement could be eliminated or made a mandatory warning.

It is a bit harder to support muticatch alone without either special treatment for final catch parameters or disjunction types.  The simplest way is to disallow assigning to multicatch parameters (i.e. making them implicitly final), and when they're rethrown use the types from the catch parameter's declaration as the thrown types for exception analysis.  This would not be a breaking change, but would be more likely to create a breaking change later if special treatment for final catch clauses were added.

EXISTING PROGRAMS:

Except as above, none.

REFERENCES

EXISTING BUGS:

No existing bugs that I am aware of.

URL FOR PROTOTYPE (optional):

An implementation of disjunctive catch parameters, but without special handling for final catch parameters:

See also