Search code examples
javajvmspecificationstry-with-resources

Return in try with resources. Is it correct behaviour of JVM?


In the following code example I expect to have 1 as a return value of method testM(). But due to the exception in TestAutoCloseable.close() method I get unexpected behaviour.

My question is: "Is it a normal behaviour of JVM?"

public static void main(String[] args) {
    ProgrammerTwo programmerTwo = new ProgrammerTwo();
    System.out.println(programmerTwo.testM());
}

int testM() {
    try (TestAutoCloseable closable = new TestAutoCloseable()) {
        System.out.println("Do first return");
        return 1;
    } catch (IOException e) {
        System.out.println("handled");
    }
    System.out.println("Do something, that shouldn't do if first return have happened");
    return 2;
}

static class TestAutoCloseable implements AutoCloseable {

    @Override
    public void close() throws IOException {
        throw new IOException();
    }
}

Because if it is normal behaviour, we shouldn't use return or break statements in try with resources statement. It should be anti-pattern.


Solution

  • The details of how the try-with-resources statement works are in this section from the JLS. In your case, it is an extended try-with-resources since it has a catch clause as defined in the below quote (note the highlighted statement at the end).

    A try-with-resources statement with at least one catch clause and/or a finally clause is called an extended try-with-resources statement.

    The meaning of an extended try-with-resources statement:

    try ResourceSpecification
         Block
    [Catches]
    [Finally]
    

    is given by the following translation to a basic try-with-resources statement nested inside a try-catch or try-finally or try-catch-finally statement:

    try {
      try ResourceSpecification          <--- exception thrown in this basic try-with-resources
          Block
    }
    [Catches]
    [Finally]
    

    The effect of the translation is to put the resource specification "inside" the try statement. This allows a catch clause of an extended try-with-resources statement to catch an exception due to the automatic initialization or closing of any resource.

    This means that closing the resource occurs within the body of the outer try block, causing an exception to be thrown and handled in the catch block, and control resumes to the statements after the extended try-with-resources statement.

    In effect, the whole method testM is equivalent to:

    int testM() {
        try {
           final TestAutoCloseable closable = new TestAutoCloseable();
           Throwable #primaryExc = null;
           try {
               System.out.println("Do first return");
               return 1;
           } catch (Throwable #t) {
               #primaryExc = #t;
               throw #t;
           } finally {
               if (closable != null) {
                  if (#primaryExc != null) {
                     try {
                        closable.close();
                     } catch (Throwable #suppressedExc) {
                        #primaryExc.addSuppressed(#suppressedExc);
                     }
                  } else {
                     closable.close();
                  }
              }
           }
        } catch (IOException e) {
           System.out.println("handled");
        }
        System.out.println("Do something, that shouldn't do if first return have happened");
        return 2;
    }