Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-4386935

(reflect) InvocationTargetException should not trap unchecked exceptions

XMLWordPrintable

    • Icon: Enhancement Enhancement
    • Resolution: Won't Fix
    • Icon: P5 P5
    • None
    • 1.3.0
    • core-libs



      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)
      ======================================================================

            iris Iris Clark
            bonealsunw Bret O'neal (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

              Created:
              Updated:
              Resolved:
              Imported:
              Indexed: