Following up on my previous post (sadly now over a year old), I wanted to talk about how to write good error messages in Java. If you haven’t read the previous post, go read it, including Eric Schrock’s Designing for Failure post. Go on, I’ll wait …..
Now that you’ve done your homework, let’s get started!
As I mentioned in my previous post, a comment on Eric’s blog covers one of my Java pet peeves:
try {
// do something which throws an exception
…
} catch (RuntimeException ux) {
throw new ExceptionHiddenException(“Something bad happened”);
}
This is error hiding. It doesn’t tell you what is wrong, in fact it explicitly discards all information in the caught RuntimeException–information that will likely tell you what is wrong. It would almost always be better to let this exception bubble up, even to the top-level, than to do what is done above. If you need to do some cleanup, like close a (possibly) open database connection, at least wrap it in a finally clause and drop information-hiding catch clauses like the one above. Or better–fix it…
What’s better? Maybe something like this:
try {
// do something which throws an exception
…
} catch (RuntimeException ux) {
throw new ModeratelyUsefulException(“Something bad happened”, ux);
}
So, we have a new exception that includes the cause exception, but we didn’t give any context, and we still fall into the trap of two of my lesser Java pet peeves: we still have getMessage() results that are largely devoid of information and useful exception data split across multiple lines. If you want to search through your application logs for errors, you might find the “Something bad happened” line, but you’d have to see the adjacent lines to get more details, making grep less handy that it could be and making you lean towards using much more heavyweight tools to look at logs. If you include the nested exception’s message in your message along with any additional context you can add, you end up with a very detailed (albeit fairly long) error message on line that is very greppable. Your support staff will love you. And so will I.
An even better example is in a patch for a bug I submitted to Maven’s Mojo project after being irritated that part of my OpenNMS build failed with no details whatsoever. The original re-thrown exception:
throw new MojoExecutionException( “Unable to copy dependency to staging area” );
Great! No nested exception, nor does it tell me what the dependency was, nor where it was trying to copy it to the staging area. The only way I could figure this out was to fire up a system call tracer (truss/strace/etc.) and see what system call failed, or fire up Maven inside of a debugger.
Here’s the improved message with context and the nested exception:
throw new MojoExecutionException( “Unable to copy dependency to staging area. Could not copy ” + artifact.getFile() + ” to ” + newLocation, ioe );
My only complaint about the new code is that it doesn’t include the nested exception’s message in the new exception’s message. This isn’t so bad with Maven, because it will show nested exceptions if there are any. If that was going to end up in a log4j log message, you can bet that I would include the nested exception’s message. Speaking of log4j, this is how I like to log exceptions:
log().warn(“Could not connect to host ” + host + “:” + port + ” due to: ” + ex, ex);
I both include the exception in the String log message and also pass it as a second argument. This gets support staff a pretty darn good amount of information on one line, as well as a stack backtrace for developers (and those super-duper support staff in the world).
I’ll stop blabbering on and share one of the simple little classes that makes me love Spring: NestedExceptionUtils, in particular its buildMessage method. This method is used by the getMessage() methods in the Exception classes that are the root of all Spring Exceptions to return their own message along with the message for a nested exception, if any. This can create huge, long messages, but they sure are chock-full-o-information. Take a peek at the NestedExceptionUtils.java source in its elegant simplicity along with the source of NestedRuntimeException to see it in action.
Happy coding, and good luck improving your logging!