0

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;
      }
  }
}
Peter Kahn
  • 12,364
  • 20
  • 77
  • 135

3 Answers3

1

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()
);
jon hanson
  • 8,722
  • 2
  • 37
  • 61
0

That's a way to go except that in your case it's not t but tryblockexception which is suppressed and you don't need to rethrow tryblockexception neither.

Other options are try-with-resources for autocloseable type or when possible making sure that the finally clause does not throw ex because it can swallow exceptions from the try block.

CodeScale
  • 3,046
  • 1
  • 12
  • 20
0

I'm not sure why you have to use a finally in such a complex way. You could also just use code that runs after the try-catch block:

Exception priorityException;
try {
  setupTestEnv();
  runTests();
} catch (Exception ex) {
  priorityException = ex;  // delayed handling
}

try {
  cleanupTestEnv();
} catch (Exception ex) {
  if (priorityException != null) {
    priorityException.addSuppressed(ex);
  } else {
    priorityException = ex;
  }
  throw priorityException;
}

Please note that I used a catch (Exception ...) instead of your Throwable because catching and handling Errors is usually not possible in a useful way. See SO question Catching Java errors and its answers for better insight.

cyberbrain
  • 3,433
  • 1
  • 12
  • 22
  • Thanks, this is actually for Jenkins CPS groovy, so the use of Throwable is there to ensure we capture both Java and Groovy level exceptions. Just using Exception alone misses some classes of Java execeptions – Peter Kahn Aug 07 '23 at 14:55