Matthew McPeak
Matthew McPeak

Reputation: 17924

Java - detect whether there is an exception in progress during `finally` block

I am considering this from the Java Language Specification:

If the catch block completes abruptly for reason R, then the finally block is executed. Then there is a choice:

  • If the finally block completes normally, then the try statement completes abruptly for reason R.

  • If the finally block completes abruptly for reason S, then the try statement completes abruptly for reason S (and reason R is discarded).

I have a block as follows:

try {
  .. do stuff that might throw RuntimeException ...
} finally {
  try {
    .. finally block stuff that might throw RuntimeException ...
  } catch {
    // what to do here???
  }
}

Ideally, I would want any RuntimeException thrown in the finally block to escape, only if it would not cause a RuntimeException thrown in the main try block to be discarded.

Is there any way in Java for me to know whether the block that is associated with a finally block completed normally or not?

I'm guessing I could just set a boolean as the very last statement of the main try block (e.g., completedNormally = true. Is that the best way, or is there something better / more standard?

Upvotes: 5

Views: 512

Answers (4)

sp00m
sp00m

Reputation: 48807

I believe the key is to not lose the original cause if any.

If we look at how try-with-resources behave:

private static class SomeAutoCloseableThing implements AutoCloseable {

    @Override
    public void close() {
        throw new IllegalStateException("closing failed");
    }

}

public static void main(String[] args) {
    try (SomeAutoCloseableThing thing = new SomeAutoCloseableThing()) {
        throw new IllegalStateException("running failed");
    }
}

We end up with:

Exception in thread "main" java.lang.IllegalStateException: running failed
    at Main.main(Main.java:16)
    Suppressed: java.lang.IllegalStateException: closing failed
        at Main$SomeAutoCloseableThing.close(Main.java:9)
        at Main.main(Main.java:17)

This stack trace is great as we see both exceptions, i.e. we don't lose the running failed one.


Implementing this without try-with-resources, the wrong way:

public static void main(String[] args) {
    SomeAutoCloseableThing thing = new SomeAutoCloseableThing();
    try {
        throw new IllegalStateException("running failed");
    } finally {
        thing.close();
    }
}

We end up with:

Exception in thread "main" java.lang.IllegalStateException: closing failed
    at Main$SomeAutoCloseableThing.close(Main.java:9)
    at Main.main(Main.java:19)

We don't know that running failed occurred too as we broke the control flow, that's quite bad if you need to debug such a case.


Implementing this without try-with-resources, the right way (in my opinion), is to "log and forget" the exception that occurred in the finally block:

public static void main(String[] args) {
    SomeAutoCloseableThing thing = new SomeAutoCloseableThing();
    try {
        throw new IllegalStateException("running failed");
    } finally {
        try {
            thing.close();
        } catch (Exception e) {
            LoggerFactory.getLogger(Main.class).error("An error occurred while closing SomeAutoCloseableThing", e);
        }
    }
}

We end up with:

17:10:20.030 [main] ERROR Main - An error occurred while closing SomeAutoCloseableThing
java.lang.IllegalStateException: closing failed
    at Main$SomeAutoCloseableThing.close(Main.java:10) ~[classes/:?]
    at Main.main(Main.java:21) [classes/:?]
Exception in thread "main" java.lang.IllegalStateException: running failed
    at Main.main(Main.java:18)

Not as good as the try-with-resources approach, but at least we know what actually happened, nothing got lost.

Upvotes: 5

diginoise
diginoise

Reputation: 7620

You could capture the original exception and re-throw it from within finally block.

Code below does just that and the exception thrown out of the method below will have the stacktrace and the cause dictated by the outer RuntimeException.

private void testException() {
    RuntimeException originalFailure = null;
    try {
        throw new RuntimeException("Main exception");
    } catch (RuntimeException e) {
        originalFailure = e;
    } finally {
        try {
            throw new RuntimeException("Final exception");
        } catch (RuntimeException e) {
          if (originalFailure != null) {
              throw originalFailure;
          } else {
              throw e;  //OR do nothing
          }
        }
    }

}

Upvotes: 1

VGR
VGR

Reputation: 44292

I assume your finally block is doing cleanup. A good way to accomplish such cleanup is to create a class that implements AutoCloseable, so your code can place it in a try-with-resources statement:

class DoStuff
implements AutoCloseable {
    public void doStuffThatMightThrowException() {
        // ...
    }

    @Override
    public void close() {
        // do cleanup
    }
}

(Notice that it does not need to be a public class. In fact, it probably shouldn’t be.)

The code in your example would then look like this:

try (DoStuff d = new DoStuff()) {
    d.doStuffThatMightThrowException();
}

As for what happens if an exception is thrown during the cleanup: it becomes a suppressed exception. It won’t show up in a stack trace, but you can access it if you really want to (which you probably won’t).

Upvotes: 3

minus
minus

Reputation: 2786

I don't think there is an idiomatic solution to this problem, partly because you normally use finally to clean-up resources disregarding completely if the code that allocated the resource terminated normally or not.

For example you finally close a connection, but the transaction will be rolled back in the catch block or committed as a last statement of the code block wrapped in the try.

Concerning an throwable thrown inside the finally block, you should decide which exception is most important to pass on to the caller. You can ultimately create your own exception which holds reference to both exceptions, in that case you need to declare a variable initialized outside the try and set inside the catch.

For example, in the following code you either complete normally or throw an exception, while having tried a recovery (rolling back a transaction) and tried a clean-up in finally.

Either can fail and you wrap what you think is the most important data in the exception you finally throw.

    private void foo() throws SQLException {

        Throwable firstCause = null;
        try {
            conn.prepareStatement("...");
            // ...

            conn.commit();
        } catch (SQLException e) {
            firstCause = e;
            conn.rollback();
            throw e;
        } finally {
            try {
                conn.close();
            } catch (Exception e) {
                throw new RuntimeException(firstCause);
                // or 
                // throw new RuntimeException(e);
                // or
                // throw new MyException(e,firstCause);
            }
        }
    }

Upvotes: 1

Related Questions