19

I noticed by accident that this throw statement (extracted from some more complex code) compiles:

void foo() {
    try {

    } catch (Throwable t) {
        throw t;
    }
}

For a brief but happy moment I thought that checked exceptions had finally decided to just die already, but it still gets uppity at this:

void foo() {
    try {

    } catch (Throwable t) {
        Throwable t1 = t;
        throw t1;
    }
}

The try block doesn't have to be empty; it seems it can have code so long as that code doesn't throw a checked exception. That seems reasonable, but my question is, what rule in the language specification describes this behavior? As far as I can see, §14.18 The throw Statement explicitly forbids it, because the type of the t expression is a checked exception, and it's not caught or declared to be thrown. (?)

Boann
  • 48,794
  • 16
  • 117
  • 146
  • 1
    +1. This actually raises the same question about multicatch, `catch (final RuntimeException | Error e) { throw e; }`, since according to §14.20, the type of `e` is `Throwable`. – ruakh Jul 27 '14 at 19:26

3 Answers3

14

This is because of a change that was included in Project Coin, introduced in Java 7, to allow for general exception handling with rethrowing of the original exception. Here is an example that works in Java 7 but not Java 6:

public static demoRethrow() throws IOException {
    try {
        throw new IOException("Error");
    }
    catch(Exception exception) {
        /*
         * Do some handling and then rethrow.
         */
        throw exception;
    }
}

You can read the entire article explaining the changes here.

Keppil
  • 45,603
  • 8
  • 97
  • 119
  • 1
    This seems to be it, but where is it in the langspec? – Boann Jul 27 '14 at 14:30
  • 14.18 says "At least one of the following three conditions must be true, or a compile-time error occurs". This isn't one of the three conditions. – Boann Jul 27 '14 at 15:26
  • Exactly. And due to the enhanced analysis introduced in Java 7 it can be determined that the third condition (_The throw statement is contained in a method or constructor declaration and the type of the Expression is assignable (§5.2) to at least one type listed in the throws clause (§8.4.6, §8.8.5) of the declaration._) is true. – Keppil Jul 27 '14 at 15:30
  • 2
    But the expression type is `Throwable` and that isn't "assignable" to `RuntimeException` or `Error`, because that would require a cast, which seems to be §5.5, not §5.2. – Boann Jul 27 '14 at 15:43
  • @Boann This is true. However, the compiler can now detect that this `Throwable` must be a `RuntimeException` or `Error`, because no other subtype of `Throwable` can be thrown in the `try`-block. **If** the throwable might be a checked exception (because such an exception might be thrown in the `try`-block), then it will complain about exactly this checked exception being thrown (as you already mentioned in the question). – Marco13 Jul 27 '14 at 15:47
8

I think that the wording in §14.18 The throw Statement, that you refer to, is a mistake in the JLS — text that should have been updated with Java SE 7, and was not.

The bit of JLS text that describes the intended behavior is in §11.2.2 Exception Analysis of Statements:

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
  • E is assignment compatible with any of C's catchable exception classes; and
  • E is not assignment compatible with any of the catchable exception classes of the catch clauses declared to the left of C in the same try statement.

The first bullet point is the relevant one; because the catch-clause parameter t is effectively final (meaning that it's never assigned to or incremented or decremented; see §4.12.4 final Variables), throw t can only throw something that the try block could throw.

But as you say, the compile-time checking in §14.18 does not make any allowance for this. §11.2.2 does not decide what's allowed and what's not; rather, it's supposed to be an analysis of the consequences of the various restrictions on what can be thrown. (This analysis does feed back into more-normative parts of the spec — §14.18 itself uses it in its second bullet point — but §14.18 can't just say "it's a compile-time error if it throws an exception it can't throw per §11.2.2", because that would be circular.)

So I think §14.18 needs to be adjusted to accommodate the intent of §11.2.2.

Good find!

ruakh
  • 175,680
  • 26
  • 273
  • 307
  • 1
    §14.18 also says "The exception types that a `throw` statement can throw are specified in §11.2.2" though. The bullet point preceding that sentence is a little badly worded with regard to this. – ntoskrnl Jul 27 '14 at 20:01
  • @ntoskrnl It does seem a bit misworded. I had missed 11.2.2 entirely. – Boann Jul 27 '14 at 20:03
  • 1
    Comparing it with an older JLS, the "three conditions" in [§14.18](http://docs.oracle.com/javase/specs/jls/se5.0/html/statements.html#14.18) are essentially unchanged, but [§11.2.2](http://docs.oracle.com/javase/specs/jls/se5.0/html/exceptions.html#11.2.2) was changed dramatically. Interesting. But this is surely the correct section, because if I insert `t = null;` before `throw t;`, so that it is no longer "effectively final", it now complains about the undeclared exception. (Accepting your answer btw as it is the most detailed. Thanks.) – Boann Jul 27 '14 at 20:12
  • @ntoskrnl: Right -- but that doesn't supersede the list of conditions. (Note that "can throw" in §11.2.2 means "can possibly throw", not "is allowed to throw".) It's not that the third condition is "a little badly worded"; it's actually quite clearly worded. (What's more, I think it's actually fine as-is. What should be changed is, there should be a fourth condition that allows the behavior that §11.2.2 implies should be allowed.) – ruakh Jul 27 '14 at 20:14
3

This behavior is described in detail in the JLS in 11.2. Compile-Time Checking of Exceptions:

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

  • E is assignment compatible with any of C's catchable exception classes; and

  • E is not assignment compatible with any of the catchable exception classes of the catch clauses declared to the left of C in the same try statement.

(Emphasis mine.)

Your second example fails because t1 is not an "exception parameter of a catch clause".

ntoskrnl
  • 5,714
  • 2
  • 27
  • 31