Why does try..finally block not register the original exception as suppressed?

1.3k views Asked by At

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?

2

There are 2 answers

5
Raniz On

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.

7
biziclop On

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.