Search code examples
javatry-finally

When Does the Finally Block Run Relative to the Return


I stumbled across an interesting error yesterday and have since fixed it, but it still was bothering me this morning, so I would like to see if anyone can shed some light on the issue.

The code in question:

final ResultSet rs = prepStatement.executeQuery();
try
{
   if (!rs.next())
   {
      throw new IllegalStateException("Expected non-empty result");
   }
   return rs.getInt(0 + 1);
}
finally
{
   rs.close();
}

Now for the part that doesn't make since. Every once in a while, the return statement will throw an exception indicating that getInt(int) has been called on a closed ResultSet. I verified that the prepared statement is not being closed anywhere in the code, and if the database was closing, I would see other errors as well. This leads me to believe that somehow, occasionally, the finally block is being executed before the return statement. The only thing I can think of is that the hotspot compiler doesn't always get this right. I'm using the Oracle JVM listed below.

java version "1.7.0_45"
Java(TM) SE Runtime Environment (build 1.7.0_45-b18)
Java HotSpot(TM) 64-Bit Server VM (build 24.45-b08, mixed mode)

I feel like I should mention that I have seen the host of other questions about this ordering, but they all seem to indicate that it is set in stone, where I seem to be witnessing something different.

Try-catch-finally-return clarification
https://stackoverflow.com/questions/20164755/the-order-of-invoking-finally-block
Does finally always execute in Java?


Solution

  • I wrote and compiled the following class

    public class Examples {
        public int answer(PreparedStatement prepStatement) throws SQLException {
            final ResultSet rs = prepStatement.executeQuery();
            try {
                if (!rs.next()) {
                    throw new IllegalStateException("Expected non-empty result");
                }
                return rs.getInt(1);
            } finally {
                rs.close();
            }
        }
    }
    

    with the following commands

    [s_delima@ml-l-sotiriosd Downloads]$ java -version
    java version "1.7.0_45"
    Java(TM) SE Runtime Environment (build 1.7.0_45-b18)
    Java HotSpot(TM) 64-Bit Server VM (build 24.45-b08, mixed mode)
    [s_delima@ml-l-sotiriosd Downloads]$ javac Examples.java 
    [s_delima@ml-l-sotiriosd Downloads]$ /usr/java/latest/bin/javap -c Examples
    Compiled from "Examples.java"
    public class Examples {
      public Examples();
        Code:
           0: aload_0       
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return        
    
      public int answer(java.sql.PreparedStatement) throws java.sql.SQLException;
        Code:
           0: aload_1       
           1: invokeinterface #2,  1            // InterfaceMethod java/sql/PreparedStatement.executeQuery:()Ljava/sql/ResultSet;
           6: astore_2      
           7: aload_2       
           8: invokeinterface #3,  1            // InterfaceMethod java/sql/ResultSet.next:()Z
          13: ifne          26
          16: new           #4                  // class java/lang/IllegalStateException
          19: dup           
          20: ldc           #5                  // String Expected non-empty result
          22: invokespecial #6                  // Method java/lang/IllegalStateException."<init>":(Ljava/lang/String;)V
          25: athrow        
          26: aload_2       
          27: iconst_1      
          28: invokeinterface #7,  2            // InterfaceMethod java/sql/ResultSet.getInt:(I)I
          33: istore_3      
          34: aload_2       
          35: invokeinterface #8,  1            // InterfaceMethod java/sql/ResultSet.close:()V
          40: iload_3       
          41: ireturn       
          42: astore        4
          44: aload_2       
          45: invokeinterface #8,  1            // InterfaceMethod java/sql/ResultSet.close:()V
          50: aload         4
          52: athrow        
        Exception table:
           from    to  target type
               7    34    42   any
              42    44    42   any
    }
    

    If you follow along with byte code instructions, you will see that at 28, the rs.getInt(1) is invoked and its value is stored at 33. The rs.close() is invoked at 35. The stored value is retrieved at 40 and returned at 41.

    What you are experiencing must come from some other point in your code.