Search code examples
javagroovyexceptionhandler

Avoiding exception hiding when finally block could throw


When you have a process which you’ve wrapped in a try catch, finally, and the action in the final could possibly throw, how do you handle exception, hiding?

Here's an example of a problem and a possible fix. Does the fix look ok, or am I re-throwing poorly?

Code with Hiding Problem

try {
  // try-block
  setupTestEnv();
  runTests();
} finally {
  // finally-block
  cleanupTestEnv();
}
  • try-block throws still result in cleanup and get thrown up the call stack
  • try-block throws where finally-block throws as well result in the loss or hiding of try-block throw as finally-block throw goes up the call stack.

Possible Workaround

Adapted from https://stackoverflow.com/a/41246027/9950

Throwable tryBlockException;
try {
  setupTestEnv();
  runTests();
} catch (Throwable t) {
  // record and throw
  tryBlockException = t;
  throw ex;
} finally {
   try {
      cleanupTestEnv()
   } catch (Throwable t) {
      if (tryBlockException != null) {
         // we have an exception from try-block
         // and that's priority exception

         // add the cleanup exception as suppressed
         // it'll be in the object and call reporting
         tryBlockException.addSuppressed(t);
         throw tryBlockException;
      } else {
         throw t;
      }
  }
}

Solution

  • Looks fine to me. I would suggest putting the bulk of the code into a re-usable method, e.g.:

    public interface Attempt {
        void apply() throws Exception;
    }
    
    public static void tryCatchFinally(
            Attempt mainF,
            Attempt finallyF
    ) throws Exception {
        Exception tryCatchException = null;
        try {
            mainF.apply();
        } catch (Exception ex) {
            tryCatchException = ex;
            throw ex;
        } finally {
            try {
                finallyF.apply();
            } catch (Exception ex) {
                if (tryCatchException != null) {
                    tryCatchException.addSuppressed(ex);
                }
            }
        }
    }
    

    then you can just use it like this:

    tryCatchFinally(
            () -> {setupTestEnv(); runTests();},
            () -> cleanupTestEnv()
    );