When an exception gets lost
April 25, 2013 | 3 min ReadImage via CC from Paul Gorbould
Recently I fell into the lost exception pitfall - when an exception thrown in a try
block gets lost because another exception is thrown in the finally
block. It wasn’t that I was not aware of the problem, but rather that I underestimated and therefore ignored it. This post describes an example where suppressed exceptions have fatal consequences, and describes possible solutions, one of them using Java 7’s try-with-resources.
The trap
Our web application is based on the Eclipse Remote Application Platform (RAP). The main loop of the session thread is wrapped into a try
block, cleanup is done in the finally
block. When the session times out, a java.lang.Error
(more precisely, a java.lang.TreadDeath
) is thrown from within the while loop:
try {
// main loop
while (!done) {
// when the session times out, an error is thrown here
...
}
} finally {
// there is a bug in this method, causing a
// NullPointerException to be thrown
cleanup();
}
Unfortunately the cleanup
method was programmed sloppily and sometimes causes a NullPointerException
. In that case the Error
is swallowed and the NullPointerException
is propagated instead.
In our case this leads to the situation where the session cannot be terminated correctly, resulting in a memory leak. This effect is similar to catching Throwable
in application code as described in Ivan Furnadjiev’s post. Our case is even worse since the error is swallowed without a trace - when Throwable
is caught, there is at least a slim chance of finding it in a log.
The situation described above is an example of a well-known problem in Java called suppressed, lost or masked exception.
Solution 1: Handle exceptions in the finally block and never let them out
Proposed for example, in the Exception-Handling Antipatterns, this solution handles exceptions in the finally
block but never propagates them. The exception thrown in the try
block is the only one visible to the outside. For our use case, logging the exceptions is a proper handling:
} finally {
try {
cleanup();
} catch (Exception ex) {
log.error(ex);
}
}
This solution is easy and quite readable, but the exceptions in the finally
block are not available anymore for processing outside of this scope.
Solution 2: Collect all exceptions in a wrapper exception
The idea is to collect all exceptions occurring in the try
and finally
blocks, and to attach them to a single exception which is thrown in the end. This article shows several code snippets on how to achieve that. This solution preserves all exceptions, but if you look at the article, you can see that the code quickly becomes unreadable and therefore unmaintainable.
Solution 3: Java 7 to the rescue
The try-with-resources statement introduced in Java 7 preserves suppressed exceptions and has a short and readable syntax. Exceptions formerly thrown in the finally
block are automatically attached as suppressed exceptions to the Throwable
thrown in the try
block. The cleanup code has to be wrapped into the close
method of a class which implements the AutoCloseable
interface:
class Cleanup implements AutoCloseable {
@Override
public void close() throws Exception {
cleanup();
}
}
...
try (Cleanup cleanup = new Cleanup()) {
while (!done) {
...
}
}
The Error
is now propagated properly, with the NullPointerException
as a suppressed exception. Note that this is a simplified version of the close
method - it should not suppress Exception
in general but rather filter out relevant ones (see the JavaDoc of the close method for details).
If the Java 6 end of life did not convince you to migrate to Java 7, maybe the try-with-resources will.