34

With the following code:

try {
    throw new RuntimeException ("main");
}
finally {
    throw new RuntimeException ("finally");
}

I get this result:

Exception in thread "main" java.lang.RuntimeException: finally
        at test.main(test.java:12)

However, with the addition of suppressed exceptions in Java 7, wouldn't it be logical for the language to register original "main" exception as suppressed when finally block itself fails with exception? Currently I have to manually emulate this:

try {
    throw new RuntimeException ("main");
}
catch (RuntimeException exception) {
    try {
        throw new RuntimeException ("finally");
    }
    catch (RuntimeException exception2) {
        exception2.addSuppressed (exception);
        throw exception2;
    }
}

to receive more useful (for understanding what's going on) result:

Exception in thread "main" java.lang.RuntimeException: finally
        at test.main(test.java:13)
        Suppressed: java.lang.RuntimeException: main
                at test.main(test.java:9)

EDIT: To clarify what I'm wondering. Current Java version is 8, suppressed exceptions are not a brand new feature. But try..finally still doesn't incorporate them. Is there something that prevents this from happening?

JasonMArcher
  • 14,195
  • 22
  • 56
  • 52
  • 1
    Shouldn't the suppressed exception be the one thrown in `finally`? – StenSoft Jun 19 '15 at 09:11
  • 1
    @StenSoft: Even if probably more logical, that would break backward compatibility completely. –  Jun 19 '15 at 09:13
  • 4
    I don't understand the close votes, to be honest. This is a perfectly legitimate question. – fge Jun 19 '15 at 09:13
  • Possibly related to http://stackoverflow.com/questions/184704/is-it-possible-to-detect-if-an-exception-occurred-before-i-entered-a-finally-blo – Codebender Jun 19 '15 at 09:16
  • Since you can emulate this fairly easily using nested try-catch blocks, a local variable holding the exception and a rethrow, I wouldn't think there was anything inherent in the VM or the JLS that would prevent this. – biziclop Jun 19 '15 at 09:23
  • @biziclop: What is in comment is only emulation, not real code. In reality you have to write finalization code in two places, because `try..catch { doX() }` is not semantically the same as `try..finally { doX() }`. –  Jun 19 '15 at 09:25
  • That's why I said you need nested try-catch blocks. But it's doable, so it could in theory be made a language feature. – biziclop Jun 19 '15 at 09:27
  • Why should it? It's completely up to the application what exactly is thrown. – ZhongYu Jun 19 '15 at 12:51
  • @bayou.io: Well, why should exceptions get a stacktrace by default? Let's make application explicitly tell exception object to fill in stacktrace when needed. –  Jun 19 '15 at 13:18
  • java doesn't know how to handle a caught exception, it's up to application logic. throwing another exception may have nothing to do with the original exception; if it does, often the original exception is embedded as the `cause` of the new exception. – ZhongYu Jun 19 '15 at 13:46

2 Answers2

9

Because try-with-resources is syntactic sugar and the Java compiler doesn't expand regular try-finally blocks in the same way.

Take a look at the following code:

try(FileInputStream fstream = new FileInputStream("test")) {
    fstream.read();
}

When compiled and then decompiled (using IntelliJ IDEA) it looks like this:

FileInputStream fstream = new FileInputStream("test");
Throwable var2 = null;

try {
    fstream.read();
} catch (Throwable var19) {
    var2 = var19;
    throw var19;
} finally {
    if(fstream != null) {
        if(var2 != null) {
            try {
                fstream.close();
            } catch (Throwable var17) {
                var2.addSuppressed(var17);
            }
        } else {
            fstream.close();
        }
    }
}

Whereas this code:

FileInputStream fstream = new FileInputStream("test");
try {
    fstream.read();
} finally {
    fstream.close();
}

Looks exactly the same when compiled and decompiled.

Now, the case could definitely be made that all finally blocks should be expanded in the same way as is done above, but for some reason that has either been overlooked or decided against.

I suggest you open a feature request for this because I think it's a sensible feature.

Raniz
  • 10,882
  • 1
  • 32
  • 64
  • 4
    I read the question as "*Why does the Java compiler not expand regular try-finally blocks in the same way*", although I think this answer still adds something useful. – Marvin Jun 19 '15 at 09:48
  • Well the question is *"Is there something that prevents this from happening?"* and the reason it's not happening is because it isn't implemented. As to *why* that isn't implemented, we can only guess or ask Oracle (possibly by opening a feature request). – Raniz Jun 19 '15 at 09:51
  • I think in real life, an exception in a finally usually happens because of closing resources exception. This is why, i think, a AutoCloseable interface must be used for exemple : class Cleanup implements AutoCloseable { @Override public void close() throws Exception { cleanup(); } } ... try (Cleanup cleanup = new Cleanup()) { // some exception is thrown here } – aurya Jun 19 '15 at 10:14
  • 1
    On the bytecode level, there is no `finally`. The reason why the second variant “looks exactly the same when compiled and decompiled”, is, that the decompiler recognizes the bytecode pattern and reconstructs the language construct. So what you are basically demonstrating, is an insufficiency of the decompiler regarding the first code example. When you use an up-to-date decompiler, it should reproduce the try-with-resources as well. As both constructs are just syntactic sugar. – Holger Jun 06 '18 at 07:05
  • Might very well be that decompilers have improved in the 3 and a half years it's been since I wrote this answer :) – Raniz Jun 07 '18 at 07:27
5

This isn't an authoritative answer but it looks like such a change would either break compatibility, or try-with-resources and try-finally would be inconsistent with each other.

The semantics in try-with-resources is that the exception thrown from the try block is propagated, with the exception thrown when invoking the close() method registered as suppressed. This makes sense from a practical point of view, you want to catch your "real" exception and then maybe also deal with the resource not closing if you want to.

But in try-finally it is the exception thrown in finally that is propagated (while the one from try is "swallowed") and therefore we have a choice of three bad solutions:

  1. Reverse the logic of try-finally to align it with try-with-resources and ruin code (or rather, bug) compatibility with all previous code.
  2. Keep propagating the "close()" exception with the try exception registered as suppressed, making the two constructs inconsistent with each other.
  3. Just leave everything as it is.

Subjectively I see 3 as less bad than 1 or 2, though it's fairly easy to argue otherwise. I suspect however that this was a dilemma the language developers were faced with and they happened to choose option 3.

biziclop
  • 48,926
  • 12
  • 77
  • 104
  • Yeah, I would argue that 2 is better than 3 (1 is obviously out of question). The reason is that from my point of view exceptions are in 90% of cases for debugging problems, only 10% are for catching. Having more valuable debug information, even if incosistent with some other case, is, in my opinion, much better than having less. –  Jun 19 '15 at 10:02
  • @doublep That part is subjective, I agree and there could be very good arguments said for all three really. I certainly didn't mean to say 3 is definitely the correct option and everything else is wrong. I just wanted to point out an important difference between the two constructs, which probably affected the decision. – biziclop Jun 19 '15 at 10:08
  • 2
    ...for example you can say that 1 is the best option because `try-finally` is conceptually broken and fixing it (and bringing it in line with `try-with-resources` is more important than maintaining backwards compatibility for the sake of code that is probably buggy anyway. – biziclop Jun 19 '15 at 10:12
  • I think 1 is the best solution. I've always thought that throwing from `finally` is a bad thing, so breaking an already broken code is not a big deal. – Svetlin Zarev Jun 19 '15 at 11:12
  • @SvetlinZarev Personally I could accept any of the three but Oracle have their own priorities. Breaking existing and working code (however dodgy that code may be and how accidental it is that it works) is usually seen as a last resort. – biziclop Jun 19 '15 at 11:45
  • 2
    The phrase “with the exception thrown in `finally` registered as suppressed” is not correct. The try-with-resource construct will register exceptions thrown *in the `close()` method* as suppressed, but if you add an explicit `finally` block, exceptions thrown there will still hide any exception thrown in the `try` block or `close()` operation. It’s worth noting that this behavior is in line with the other questionable behavior that any control flow statement in `finally`, i.e. `break`, `continue`, or `return`, will hide any exception thrown in the `try` block. – Holger Jun 06 '18 at 08:51
  • @Holger Yes, that's what I meant, thanks for the clarification. – biziclop Jun 06 '18 at 10:10