Proper exception handling is an important part of writing correct Java programs, and unfortunately also a rather complex one since the control flow is invisible compared to returning and checking error codes. You need to be aware of how exceptions work for two reasons:
- Error reporting, and gracefully handling of errors generated by subsystems.
- Guarding resources, preventing resource leaks
This article will describe simple rules and useful patterns to help you write exception-safe code.
Exception Basics
Exceptions can occur at any line of your program. If an
exception is thrown, the code that is currently running will be
aborted and control will jump to the nearest enclosing exception
handler. As such, Exceptions are very useful to signal error
conditions without cluttering your code with a cascade of if
statements to see whether calls to statements succeeded. Your code
will describe the Success flow of your program, while error
handling will be confined to specific areas of code (so-called
Exception handlers).
You define exception handlers in the following way:
try {
.
.
.
} catch (SomeExceptionType ex) {
.
} catch (OtherExceptionType ex) {
.
} finally {
.
}
catch and finally clauses can be
ommitted if you don’t need them. That is, you can just define a
try…catch or try…finally handler if you
don’t need the other one.
Rule 1 of exception handling
`try…catch` blocks are used for error handling. `try…finally` blocks are used for resource guarding. This article assumes you know how the `try…` blocks work, what is caught by `catch(...)` clauses (viz. the Exception class hierarchy) and when the `finally` clause is executed. If you don’t yet, JFGI ;).Checked vs. Unchecked Exceptions
Java introduces the concept of Checked Exceptions. Checked Exceptions are special exception types of which the compiler checks whether you handle them, or pass the responsibility of handling them off to your caller. It is forbidden not to handle or pass off responsibility for checked exceptions. These are the exceptions that your editor warns you about if you fail to handle them (and it will usually offer to generate a default exception handler for you that will simply log the error and not do anything else).
Rule 2 of exception handling
NEVER simply let your editor generate a default exception handler for you! The lines of code where checked exceptions are thrown are RARELY the spot where you should be handling them by simply logging them and going on.Checked exceptions sound like a good idea in theory (more help
from the compiler preventing you from making stupid mistakes!). In
practice it turns out that this concept needlessly litters your
code with exceptions-related boilerplate code: can you really
“handle” a FileNotFoundException gracefully deep in the call stack
where your openFile() method is called? What, do you retry with a
different filename? No, the proper response is to display this
error to the user, have him enter a different filename and then
try again. However, because FileNotFoundException is a checked
exception, you’re forced either to deal with the exception then
and there (you can’t) or add the clause throws
FileNotFoundException to every method signature in the call chain.
If one of those methods is in an external library that you can’t
modify, you’re SOL.
A more practical way to deal with checked exceptions that you
can’t handle on-the-spot is to turn them into unchecked
exceptions. Unchecked exceptions are exceptions that inherit from
RuntimeException, so you can simply wrap the checked
exception in a RuntimeException and throw that
instead:
try {
.
.
.
} catch (IOException ex) {
throw new RuntimeException(ex);
}
Yes, you will lose exception type information in this way. In
90% of cases this doesn’t matter; when it does, define a custom
subclass of RuntimeException and throw that instead, then catch it
in some exception handler somewhere. Alternatively, you can
inspect the exception stack with Exception.getCause().
Error Handling: Logging vs. Rethrowing vs. Doing something intelligent
Rule 3 of exception handling
Only catch exceptions if you can deal with them! (Rule 3a: or if you intend to rethrow exceptions to change their type)“Dealing with them” can be as complex as retrying the operation or choosing a different strategy, or as simple as showing an error message to the user. Most errors you can’t do anything about, so your only recourse is to show them to the user, and let him figure out what is wrong and how to correct the situation. In fact, you should always put the infrastructure to do this in place anyway, even if you intend to catch and really handle some exceptions along the way. There is no way you can handle all exceptions, and keeping the user informed with error messages is always a good idea. Even if the user can’t correct the error immediately, he will come to you with a descriptive error message readily burned into his mind, instead of a generic “something went wrong” message after which you can go dig through logs.
With regards to logging: log as much as you want, but be careful: you may be logging the exact same exception many times in the log, which will confuse you later on as you start digging through the log. There’s no real need to do this, as the final stack trace of the exception will show all the root exception causes and their stack traces as well.
Error pattern: log and show error to user
Idea: you install a generic error handler at the very highest level in your application. Normally, it is bad form to catch exceptions of all types but in this case it is acceptable since you really want to catch any and all errors to prevent a system abort of your application.
In a web application, you typically install this error handler before you start handling a HTTP request. In a GUI application, you install the error handler in the event loop. In case you use a framework, that framework should provide a way to install an exception handler at the dispatcher level.
try {
switch (whatToDo) {
case 1: doAction1(); break;
case 2: doAction2(); break;
case 3: ...; //
// The actual handler methods called here are expected to throw exceptions
// in case anything goes wrong, and not to handle those themselves
.
.
}
} catch (Exception ex) {
log.error(ex.getMessage(), ex);
displayErrorToUser(ex.getMessage());
}
Use Exception.getMessage() to obtain the error
message for the exception. Conversely, if you throw exceptions,
include a descriptive message to make the contents of this field
useful to the user.
In a web application, displayErrorToUser() will
simply output a nicely formatted error page, in a GUI application
it may popup an alert box, etc. In a console application, it would
typically print the error message to System.stderr
and exit with an error code. In all cases, the state of the
application returns to where the user can make another call: for
the web application and the console application, the
request/application is finished and the user can try another
invocation with different parameters. In the GUI application, the
event loop continues and the user can click another button to try
again.
Error pattern: retry N times
In some cases of errors, you may want to retry an operation a number of times. In these instances, you typically only do this under very specific circumstances, in which you know it makes sense to retry. In this case, you don’t catch Exception but the specific exception type that you’re trying to recover from.
In this example, we try retrieving a webpage N times in the
face of a NoSuchHostException (indicating that we are
currently not connect to a network).
int tries = 10;
while (true) {
try {
readWebPage();
} catch (NoSuchHostException ex) {
tries—;
if (tries <= 0)
throw ex; // All tries used up, give up and throw exception to caller
try {
Thread.sleep(10000); // Sleep to avoid hammering the server and our CPU
} catch (InterruptedException ex) {
// Java threading exception that we can ignore
}
}
}
Resource Cleanup
It is important to always guarantee freeing up external resources that the JVM does not have control over. Typical examples of these that you will use in your application include database connections, file handles, network sockets etc. Anything that deals with the outside world (that is to say, anything that does I/O) typically needs to be cleaned up.
In the face of exceptions, it is tricky to guarantee cleanup of allocated resources. See the following piece of code:
Resource r = allocateResource();
doSomethingWith(r);
r.free();
If an exception is thrown in doSomethingWith(),
the execution of this piece of code will abort, and hence
r.free() will never be called: your program has a
resource leak! If this happens often enough, the system may run
out of resources and your application will cease to work,
necessitating an application/server restart.
Fortunately, this is what the finally block was
invented for.
Rule 4 of exception handling
Once you acquire a resource, you become responsible for releasing it. **Always** start a `try…finally` block after acquiring a resource.The proper way to write this code is:
Resource r = allocateResource();
try {
doSomethingWith(r);
} finally {
r.free();
}
This guarantees that:
- The resource will be released if it was allocated, whether
or not
doSomethingWith()succeeds or not. - The resource will not be released if it was never
succesfully acquired: that means an exception would have
occurred within
allocateResource(), before thetryblock was even started. It is the responsibility ofallocateResource()to either allocate and return or don’t allocate at all; we will describe how to guarantee this in a following section.
Guarding multiple resources
A typical example of where you need resource guarding is when
doing database operations. These typically involve a
Connection, PreparedStatement and
ResultSet, each of which must be closed separately or
you will have a database connection leak!
The simple (and proper) way to deal with this is to guard every resource individually:
Connection c = openConnection();
try {
PreparedStatement s = c.prepareStatement(“...”);
try {
ResultSet r = s.execute();
try {
doSomethingWith(r);
} finally {
r.close();
}
} finally {
s.close();
}
} finally {
c.close();
}
If you don’t like the christmas tree-like indenting of this
piece of code, you can also achieve (almost) the same effect with
a single try…finally block:
Connection c = null;
PreparedStatement s = null;
ResultSet r = null;
try {
c = openConnection();
s = c.prepareStatement(“...”);
r = s.execute();
doSomethingWith(r);
} finally {
if (r != null) r.close();
if (s != null) s.close();
if (c != null) c.close();
}
This code has almost the same behaviour, except for one tricky
detail: the close() methods can also throw exceptions! For
example, if an exception happens while executing r.close(), the
finally handler will be aborted and the ResultSet and Connection
will not be closed, again leaking resources.
It is actually rather annoying that closing these resources can
throw exceptions, because there is nothing that we can (or want
to) do about this anyway. Either the resource doesn’t need to be
closed because it already was (good enough for cleanup purposes!)
or it can’t be closed for some other reason, and we can’t do much
about that anyway except logging it. An easy fix is to create a
helper close() method that will eat (and log) exceptions related
to closing resources, guaranteeing that our finally block will run
to completion:
try {
.
.
.
} finally {
if (r != null) close(r);
if (s != null) close(s);
if (c != null) close(c);
}
// ...
void close(ResultSet r) {
try {
r.close();
} catch (Exception ex) {
log.error(ex);
}
}
void close(PreparedStatement s) { /* ... */ }
void close(Connection c) { /* ... */ }
Tip: take a look at the D Programming Language, which has a much more elegant method of writing resource guards.
Resource ownership, and writing a resource allocator function
In the previous sections, we took for granted that the
allocateResource() function makes the following guarantee:
- The resource is allocated if and only if the method returns without throwing an exception.
At that point, the caller of the
allocateResource() method becomes the owner of the
resource, and becomes responsible for deallocating it. Thinking
about the ownership of a resource is very useful if you’re trying
to decide who is responsible for deallocating an allocated
resource. For example, the following is typically an antipattern:
void foo() {
Resource r = allocateResource();
bar(r);
}
void bar(Resource r) {
try {
// Do something with r
// ...
} finally {
r.close();
}
}
foo() triggers allocation of the resource, and
then becomes the owner of that resource. However, the resource is
ultimately cleaned up by bar(). Apparently, ownership of the
resource is passed off along the way, but this is not clearly
defined and behaviour is non-local (you cannot tell from looking
at foo() that the resource is eventually cleaned up). Also, now
you cannot call bar() on the resource anymore without the resource
being released (which may be something that you want to do). All
in all, it is better to keep the ownership of the resource
confined to foo().
Rule 5 of exception handling
The class or method that allocates a resource assumes ownership of this resource and is responsible for cleaning it up.In some exceptional cases, it may be necessary to deviate from this pattern (due to constraints imposed by external APIs). If a method or class assumes ownership of a resource that is passed into it, document this fact clearly in its docstring!
Now let’s look at how to write an allocator function that deals properly with resource ownership and makes the proper guarantees about the resource (that is, allocated and ownership is passed off, or not allocated at all). Note: you might not need to do this! Often, allocator functions provided by various framework APIs may very well suffice for your purposes, and you could regard the resource setup phase discussed below could be rolled into your “do something with r” phase. Nevertheless, if you do design convenience functions or otherwise, the patterns described here still apply.
An allocator function typically looks like this:
Resource allocateResource() {
// Create
Resource r = createResource();
// Setup
r.setFoo(foo);
r.setBar(bar);
// Pass to caller
return r;
}
The point is that there are two steps:
- Creating the resource, typically done in a library function
- Initializing the resource, setting up various members and other stuff
The catch is that during setup, exceptions can happen, causing the allocator functions to abort. At this point, the resource to the reference will be lost and it will remain unreleased forever and, hence, leak. The trick is to catch any exceptions inside the setup block and in the exception handler, release the resource and re-throw the exception. This guarantees that the exception is signaled to the caller and the resource will be released properly. In Java, we have to deal with checked vs. unchecked exceptions. We could choose to declare checked exceptions, or simply convert all exceptions to unchecked exceptions. In the following example, we’ll do the latter:
Resource allocateResource() {
Resource r = createResource();
try {
r.setFoo(foo);
r.setBar(bar);
return r;
} catch (RuntimeException ex) {
r.free();
throw ex; // Unchecked, simply throw
} catch (Exception ex) {
r.free();
throw new RuntimeException(ex); // Checked, wrap
}
}
As you can see, the exception handlers are rather ugly because we have to deal with the checked/unchecked distinction. Another way that is slightly less repetitive code but also more obscure would be to do run-time type inspection on the exception object:
Resource allocateResource() {
Resource r = createResource();
try {
r.setFoo(foo);
r.setBar(bar);
return r;
} catch (Exception ex) {
r.free();
if (ex instanceof RuntimeException)
throw (RuntimeException)ex;
else
throw new RuntimeException(ex);
}
}
You can spot the similarities between the try…finally pattern
used for guarding resources where you assume ownership, and the
try…catch pattern used for guarding resources where you prepare to
pass off ownership.
Rule 6 of exception handling
Guard resources with try…finally if you assume ownership, and with try…catch if you temporarily assume ownership before passing off ownership.Combining error handling with resource cleanup
A lot of I/O related code throws IOException exceptions, and it
is declared to throw these both on resource allocation, resource
use and resource cleanup. Because IOException is a checked
exception, you are required to catch these exceptions (or declare
that you pass responsibility for catching these exceptions off to
your caller). If you want to transform the checked exception into
an unchecked exception, do so at the highest level.
Wrap your entire method body in a try…catch block
that transforms the checked exception into an unchecked exception,
and don’t try to deal with each invocation that can trigger an
unchecked exception separately. Then, put nested try…finally
blocks inside that exception handler to guard resources allocated
inside the block.
Bad:
void writeOut(File file) {
Reader r;
try {
Reader r = new InputStreamReader(new BufferedInputStream(new FileInputStream(file));
} catch (IOException ex) {
throw new RuntimeException(ex);
}
try {
String s = null;
do {
try {
s = r.readLine();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
if (s != null) System.out.println(s);
} while (s != null);
} finally {
try {
r.close();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
Good:
void writeOut(File file) {
try {
Reader r = new InputStreamReader(new BufferedInputStream(new FileInputStream(file));
try {
for (String s = r.readLine(); ; s = r.readLine())
System.out.println(s);
} finally {
r.close();
}
} catch (IOException ex) {
throw new RuntimeException(ex); // Checked to unchecked
}
}
Of course, the “bad” example here is quite pathological, but the difference illustrates the difference between integral and ad-hoc exception handling quite well.
Studious readers may notice that even the so-called “good”
example is not free from a resource leak! If an exception is
thrown inside either the InputStreamReader or BufferedInputStream
constructors (unlikely as it is), the open file handle resource
held by the FileInputStream will be leaked. That is why I usually
overload functions that do I/O to accept both an InputStream as
well as a File: the version that handles the File input will take
ownership of the file and guarantees that the file handle will be
released. As an added bonus, our API just became more flexible
because it now can deal with data that arrives from other sources
than just a file, such as over the network.
void writeOut(File file) {
try {
InputStream is = new FileInputStream(file);
try {
writeOut(is);
} finally {
is.close();
}
} catch (IOException ex) {
throw new RuntimeException(ex); // Checked to unchecked
}
}
void writeOut(InputStream is) {
try {
Reader r = new InputStreamReader(new BufferedInputStream(is));
for (String s = r.readLine(); ; s = r.readLine())
System.out.println(s);
} catch (IOException ex) {
throw new RuntimeException(ex); // Checked to unchecked
}
}
We do not close the Reader anymore because closing the Reader
would also trigger a close() on the underlying InputStream, and
the writeOut(InputStream) method does not have ownership of that
stream. Instead, we rely on the fact that there are no external
resources associated with the Reader, and that it will eventually
be garbage collected so we can avoid cleaning up that resource.
However, in the writeOut(File) call is used, note that the
FileInputStream will be cleaned up regardless (because that method
owns that stream).
Conclusion
This article has shown a number of principles and patterns to apply when writing code that deals with exceptions, and should help you writing write code that behaves properly and safely in the face of exceptions. If you disagree with any of this, don’t hesitate to leave a comment!