Andy Turner Andy Turner - 1 month ago 8
Java Question

Rethrowing Exception without requiring throws Exception?

Consider the following code:

static void main(String[] args) {
try {
} catch (Exception e) {
throw e;
}
}


This code compiles without having to add
throws Exception
to the method signature. (It behaves similarly with
Throwable
in place of
Exception
, too).

I understand why it can be run safely, in that
Exception
can't actually be thrown in the
try
block, so a checked exception cannot be thrown; I'm interested to know where this behaviour is specified.

It's not simply that the
throw e
is never reached: the following code also compiles:

static void stillCompilesWithThrownUncheckedException() {
try {
throw new NullPointerException();
} catch (Exception e) {
throw e;
}
}


But if you throw a checked exception, it doesn't compile, as I expect:

static void doesNotCompileWithThrownCheckedException() {
try {
throw new Exception();
} catch (Exception e) {
throw e; // error: unreported exception Exception; must be caught or declared to be thrown
}
}


In JLS Sec 11.2.2, it says:


A
throw
statement (§14.18) whose thrown expression has static type E and is not a final or effectively final exception parameter can throw E or any exception class that the thrown expression can throw.


My interpretation of this statement is that
throw e
can throw
Exception
, because the static type of
e
is
Exception
. And then, in
JLS Sec 11.2.3
:


It is a compile-time error if a method or constructor body can throw some exception class E when E is a checked exception class and E is not a subclass of some class declared in the throws clause of the method or constructor.


But it's not a compile-time error in the first two cases. Where is this behavior described in the language spec?




Edit: having marked it a dupe, I was going to ask the follow-up question: why isn't
throw e;
considered unreachable in the first example
.

The answer was much easier to find in JLS Sec 14.21:



  • A catch block C is reachable iff both of the following are true:


    • Either the type of C's parameter is an unchecked exception type or Exception or a superclass of Exception, or some expression or throw statement in the try block is reachable and can throw a checked exception whose type is assignable to the type of C's parameter. (An expression is reachable iff the innermost statement containing it is reachable.)

      See §15.6 for normal and abrupt completion of expressions.

    • There is no earlier catch block A in the try statement such that the type of C's parameter is the same as or a subclass of the type of A's parameter.





Both of these are true (it's of type
Exception
, and there's no earlier catch block), so it's "reachable". I guess the effort of calling out an empty
try
block as a special case was too great for such a marginally-useful construct.

VGR VGR
Answer

I believe the very next paragraph of section 11.2.2 answers the question:

A throw statement whose thrown expression is a final or effectively final exception parameter of a catch clause C can throw an exception class E iff:

  • E is an exception class that the try block of the try statement which declares C can throw; and

So, throw e; “can throw” only exceptions which the corresponding try-block “can throw,” where the latter is defined by the actual statements in the try-block.

Obviously an empty try-block does not qualify as a “can throw” section for any exception class. Your second example “can throw” NullPointerException, and since the catch-block “can throw” only the exception that the try-block “can throw,” the catch-block too can throw only the unchecked NullPointerException.

Your third example’s try-block “can throw” java.lang.Exception itself, therefore the catch-block “can throw” java.lang.Exception, so java.lang.Exception must be caught or declared to be thrown.