JDK-4386935 : (reflect) InvocationTargetException should not trap unchecked exceptions
  • Type: Enhancement
  • Component: core-libs
  • Sub-Component: java.lang:reflect
  • Affected Version: 1.3.0
  • Priority: P5
  • Status: Closed
  • Resolution: Won't Fix
  • OS: generic
  • CPU: generic
  • Submitted: 2000-11-08
  • Updated: 2012-09-28
  • Resolved: 2001-11-06
Description

Name: boT120536			Date: 11/07/2000


java version "1.3.0"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.0)
Classic VM (build 1.3.0, J2RE 1.3.0 IBM build cx130-20000623 (JIT enabled:
jitc))


I have a general complaint about how reflection handles Throwables during
execution.  InvocationTargetException is a checked error, yet it traps all
Throwables instead of only checked errors.  Thus, a user is required to catch
Errors and RuntimeExceptions.  This is a safe approach to take, but adds
needless clutter -- the reason Error exists is so that users do not have to
catch something that can be difficult or impossible to recover from unless they
really mean to.

I would like the reflection methods, like Constructor.newInstance(), to be more
in line with how Class.newInstance() handles Errors (it does not trap or wrap
them): if a user wants to worry about OutOfMemoryError during reflection, then
they can explicitly catch it; but if they don't want to worry, it is not caught
for them inside a checked exception.

It would be nicer if Reflection methods wrapped any caught Throwable in one of
three Throwables:
InvocationTargetException (hereafter ITException, for brevity) - wraps any
checked exception, and is checked so that it must be dealt with
InvocationTargetRuntimeException (ITRuntimeException) - wraps unchecked runtime
exceptions
InvocationTargetError (ITError) - wraps any Error.

This would be more like UndeclaredThrowableException, used by Proxy, which wraps
only checked exceptions that fall through the cracks of a method's declaration.

I can think of several methods for improving this, but each has its weaknesses:

Scenario 1:
ITUncheckedException extends ITException
ITRuntimeException extends ITUncheckedException
ITError extends ITUncheckedException

This is backward compatible with existing class files, makes it easier to
rethrow errors without conditionals in new code, but does not solve the problem
of being forced to trap errors:

try {
  Method.invoke()...
} catch (InvocationTargetError err) {
  throw err.getTargetError();
} catch (InvocationTargetException ex) {
 ...//handle checked exceptions
}

Scenario 2:
ITError extends Error
ITRuntimeException extends RuntimeException

This is also backward compatible with class files, and allows unchecked
exceptions to remain unchecked, but if someone had been checking if the
ITException wrapped a RuntimeException or Error, that check now fails.

Scenario 3:
interface ITException extends ThrowableInterface
class ITCheckedException extends Exception implements ITException
class ITRuntimeException extends RuntimeException implements ITException
class ITError extends Error implements ITException

This would require an addition to the JLS creating ThrowableInterface, which in
addition to the class Throwable can be thrown and caught.  (This addition could
be useful, however, in other places).  And, since ITException can presently be
instantiated and is not a final class, any compiled code that directly creates
an ITException or extends it would break.

Scenario 4:
interface UncheckedException
class Error extends Throwable implements UncheckedException
class RuntimeException extends Exception implements UncheckedException
class ITRuntimeException extends ITException implements UncheckedException
class ITError extends ITException implements UncheckedException
class ITCheckedException extends ITException

This would require a modification in the JLS and to new compilers so that any
Throwable class is checked unless it implements UncheckedException.  Of course,
you will have to be careful on how UncheckedException works, maybe making it
only a package interface in java.lang so that it won't be abused.  In this way,
it is possible to add any number of unchecked Throwables in core Java that do
not extend Error or RuntimeException, which leaves room in Java for any future
unchecked exceptions that are needed.
This change would be backward compatible with all existing code: ITException is
still a class, and code that catches ITException instead of a more specific
subclass can still deal with any wrapped Throwable as it sees fit.

Scenario 5:
Modify ITException to ignore all unchecked exceptions; and only trap checked
ones.

This is compatible with existing classfiles, but again, if a user had been
expecting a RuntimeException to be wrapped in an ITException, that check fails.
Plus, it is nice knowing that the reflection methods can throw only a limited
set of Exceptions, rather than any exception possible during the reflection.

Scenario 6:
Modify ITException to ignore only asynchronous exceptions (JLS 11.3.2) - those
thrown by stop() in Threads, and InternalError and its subclasses.

While this has the same problems as scenario 5 for changing user's expectations,
it affects a smaller subset of exceptions, and the ones it would ignore should
generally be ignored.  Thus, threaded programs and virtual machine problems
would have better behavior.



Of all these scenarios, I think version 4 is the best solution to the problem.


============
// Example program - OutOfMemoryError is usually fatal and unrecoverable, so I
// would rather not be forced to catch it wrapped inside the checked
// InvocationTargetException
import java.lang.reflect.*;

class Memory {
  public static void main(String[] args) {
    try {
      System.out.println("Using Class.newInstance()");
      Memory.class.newInstance();
    } catch (Exception e) {
      System.out.println("Caught " + e);
    } catch (Error err) {
      System.out.println("ignored " + err);
    }
    try {
      System.out.println("Using java.lang.reflect");
      Memory.class.getConstructor(null).newInstance(null);
    } catch (Exception e) {
      // As OutOfMemoryError is not an Exception, but an Error,
      // I shouldn't get here; but InvocationTargetException traps it
      System.out.println("Caught " + e);
    } catch (Error err) {
      System.out.println("ignored " + err);
    }
    try {
      System.out.println("Using direct call");
      new Memory();
    } catch (Error err) {
      System.out.println("ignored " + err);
    }
  }

  public Memory() {
    int size = 1;
    int[] array;
    while (true) {
      array = new int[size];
      size *= 2;
    } // should eventually throw an OutOfMemoryError
  }

}
/* Produces this output:
Using Class.newInstance()
ignored java.lang.OutOfMemoryError
Using java.lang.reflect
Caught java.lang.reflect.InvocationTargetException
Using direct call
ignored java.lang.OutOfMemoryError
*/
(Review ID: 107972) 
======================================================================

Comments
WORK AROUND Name: boT120536 Date: 11/07/2000 Manually rethrow all trapped unchecked errors that I am not interested in trapping: try { Method.invoke()... } catch (InvocationTargetException e) { Throwable e1 = e.getTargetException(); if (e1 instanceof Error || e1 instanceof RuntimeException) throw e1; ... // handle checked exceptions } catch (Exception e) { ... } ======================================================================
11-06-2004

EVALUATION The submitter is correct, we could be more consistent with how we handle exceptions in reflection. Unfortunately, at this point it is impossible for us to modify the behaviour as suggested because doing so would cause serious compatibility problems. -- iag@sfbay 2001-11-05
05-11-2001