1

Here's the code.

public class TestTest {
    public static void main (String[] args) throws Exception {
        try {
            run();
        } catch(Exception e) {
            printSuppressedExceptions(e);
        }
    }

    public static void printSuppressedExceptions(Throwable t) {
        System.out.println(t);
        System.out.println("suppressed exceptions: " + t.getSuppressed().length);
    }

    public static void run() throws Exception {
        try(MyResource r = new MyResource("resource");) {
            System.out.println("try");
            System.getProperty("").length(); // throws illegalArgumentException
        } catch(Exception e) {
            printSuppressedExceptions(e);
            throw e;
        } finally {
            new MyResource("finally").close();
        }
    }   
}

class MyResource implements AutoCloseable {
    private final String name;

    public MyResource(String name) {
        this.name = name;
    }

    @Override
    public void close() throws Exception {
        throw new Exception("exception" + " from " + this.name);
    }
}    

Since exception thrown from try block suppressed the exception from resource, I got "suppressed exceptions: 1" at first which was understandable. But when an exception was thrown from finally, it seemed like all suppressed exceptions disappeared because I got "java.lang.Exception: exception from finally" followed by "suppressed exceptions: 0" which I think it should be 1. I browsed the Java tutorials and it definitely says

However, in this example, if the methods readLine and close both throw exceptions, then the method readFirstLineFromFileWithFinallyBlock throws the exception thrown from the finally block; the exception thrown from the try block is suppressed.

From The try-with-resources Statement

How could it happen?

Fildor
  • 14,510
  • 4
  • 35
  • 67
choasia
  • 10,404
  • 6
  • 40
  • 61
  • 1
    Just speculating: Could it be that it is, because there are 2 exceptions thrown? I expect one from `r.close()` when try-with-resources closes r and another one from the additional object created and closed in the finally block ... – Fildor Nov 09 '16 at 09:54
  • 1
    Also, the cited text refers to an example of prior Java 7 usage of the `try{ read() }finally{ close() }`. I suggest you read the whole paragraph and go on reading the "Suppressed Exceptions" Paragraph. – Fildor Nov 09 '16 at 09:58
  • Thanks. I think @tuempl just solved my confusion. _But this time the word suppressed used in the java tutorial does not stand for "suppressed and added to the actually thrown exception" but "suppressed and lost to nirvana"._ – choasia Nov 10 '16 at 00:40

1 Answers1

1

Here is code that does what you would expect:

public class TestTest {
    public static void main (String[] args) throws Exception {
        try {
            run();
        } catch(Exception e) {
            printSuppressedExceptions(e);
        }
    }

    public static void printSuppressedExceptions(Throwable t) {
        System.out.println(t);
        System.out.println("suppressed exceptions (" + t.getSuppressed().length + "):");
        for (Throwable suppressed : t.getSuppressed()) {
            System.out.println("  - " + suppressed);
        }
    }

    public static void run() throws Exception {
        Exception exceptionFromCatch = null;
        try(MyResource r = new MyResource("resource");) {
            System.out.println("try");
            System.getProperty("").length(); // throws illegalArgumentException
        } catch(Exception e) {
            exceptionFromCatch = e;
            printSuppressedExceptions(e);
            throw e;
        } finally {
            try {
                new MyResource("finally").close();
            } catch (Exception e) {
                if (exceptionFromCatch!=null) {
                    e.addSuppressed(exceptionFromCatch);
                }
                throw e;
            }
        }
    }   
}

class MyResource implements AutoCloseable {
    private final String name;

    public MyResource(String name) {
        this.name = name;
    }

    @Override
    public void close() throws Exception {
        throw new Exception("exception" + " from " + this.name);
    }
}

So lets go trough the try-with-resource part of your code (as introduced in JDK 1.7.0) and see what happens (see What is the Java 7 try-with-resources bytecode equivalent using try-catch-finally? for more details):

  • the try-with-resource block MyResource r = new MyResource("resource") is executed
  • the try block is executed and throws an IllegalArgumentException
  • the try-with-resource block calls close() for all resources (in your example only one)
  • close() throws an exception, but since the exception from the try block has priority the exception from thrown by close() is suppressed and added via addSuppressed(..)

So that part works like you expected from reading the tutorial.

And now the try-catch-finally part of your code (as in JDK 1.6 and earlier):

  • the try block is executed and throws an IllegalArgumentException
  • (the catch block behaves the same way as if there was no catch block)
  • the finally block is executed and throws an Exception
  • the exception from the finally block has priority and the one from the try block is suppressed

But this time the word suppressed used in the java tutorial does not stand for "suppressed and added to the actually thrown exception" but "suppressed and lost to nirvana". So it still behaves as in JDK 1.6 and earlier and does not make use of the newly introduced addSuppressed(..) getSuppressed() functionality. That's the reason it doesn't behave like you expected.


I would argue the behaviour you expected wouldn't be logical either. I would like it to behave like this:

...
        } finally {
            try {
                new MyResource("finally").close();
            } catch (Exception e) {
                if (exceptionFromCatch!=null) {
                    exceptionFromCatch.addSuppressed(e);
                } else {
                    throw e;
                }
            }
        }
...

That would always give priority to the exception from the try block (as implemented with the new try-with-resource feature) and add the exception from the catch block as suppressed to the list. But that would break compatibility with JDK 1.6, so I guess that's the reason why it doesn't behave like that.

Community
  • 1
  • 1
tuempl
  • 109
  • 3
  • Aha, just thought all suppressed exceptions would be added into the actually thrown exception since 1.7 which turned to be totally wrong. – choasia Nov 10 '16 at 00:36