Marginally better stack traces

So as a developer you get to spend a few hours a day trawling through log files and deciphering stack traces to find out why Bob in accounting saw two paypal payments instead of one or something. Your task, if you choose to accept it, is to try to stop your eyes glazing over whilst poring over megabytes of irrelevant crap.
My typical stack trace looks a bit like this:
A Contrived Example
org.springframework.jdbc.BadSqlGrammarException: StatementCallback; bad SQL grammar [SELECT flarge FROM tableOfFlonges]; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'flarge' in 'field list' at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:220) at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:72) at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:407) at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:458) at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:466) at org.springframework.jdbc.core.JdbcTemplate.queryForList(JdbcTemplate.java:497) at com.contrived.web.action.MainAction.execute(MainAction.java:33) at org.apache.struts.action.RequestProcessor.processActionPerform(RequestProcessor.java:425) at com.randomnoun.common.webapp.struts.CustomRequestProcessor.processActionPerform(CustomRequestProcessor.java:606) at org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:228) at com.randomnoun.common.webapp.struts.CustomRequestProcessor.process(CustomRequestProcessor.java:723) at org.apache.struts.action.ActionServlet.process(ActionServlet.java:1913) at org.apache.struts.action.ActionServlet.doGet(ActionServlet.java:449) at com.randomnoun.common.webapp.struts.CustomActionServlet.doGet(CustomActionServlet.java:55) at javax.servlet.http.HttpServlet.service(HttpServlet.java:621) at javax.servlet.http.HttpServlet.service(HttpServlet.java:722) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:224) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:169) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:168) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98) at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:927) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407) at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:987) at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:579) at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:309) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603) at java.lang.Thread.run(Thread.java:722) Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'flarge' in 'field list' at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:525) at com.mysql.jdbc.Util.handleNewInstance(Util.java:406) at com.mysql.jdbc.Util.getInstance(Util.java:381) at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1051) at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3563) at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3495) at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1959) at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2113) at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2687) at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2616) at com.mysql.jdbc.StatementImpl.executeQuery(StatementImpl.java:1464) at org.springframework.jdbc.core.JdbcTemplate$1QueryStatementCallback.doInStatement(JdbcTemplate.java:443) at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:396) ... 29 more
Which you will immediately notice from the first line is because the SQL statement
SELECT flarge FROM tableOfFlonges
is grammatically incorrect.
All you have to do now is find out which bit of code you need to modify in order to fix the bug.
One of the nice, yet underrated things about Java, is that you determine fairly quickly whose code is running, due to the use of packages in the class names. In this example, there’s code from:
- the particular client I’m working for (
com.contrived
), - my own code and libraries (
com.randomnoun
), - the Apache Tomcat application server (
com.apache.tomcat
) - the MySQL JDBC library (
com.mysql
) - a JDBC abstraction mechanism (
com.springframework.jdbc
) - the struts MVC framework (
com.apache.struts
) - etc
What I would like though is something more like the following, in which I assume for the time being that the fault in the code more often than not lies with something that I’ve written.
A Contrived Example with Special Sauce
org.springframework.jdbc.BadSqlGrammarException: StatementCallback; bad SQL grammar [SELECT flarge FROM tableOfFlonges]; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'flarge' in 'field list' at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:220) at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:72) at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:407) at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:458) at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:466) at org.springframework.jdbc.core.JdbcTemplate.queryForList(JdbcTemplate.java:497) at com.contrived.web.action.MainAction.execute(MainAction.java:33, ver 1.7) at org.apache.struts.action.RequestProcessor.processActionPerform(RequestProcessor.java:425) at com.randomnoun.common.webapp.struts.CustomRequestProcessor.processActionPerform(CustomRequestProcessor.java:606, ver 1.23) at org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:228) at com.randomnoun.common.webapp.struts.CustomRequestProcessor.process(CustomRequestProcessor.java:723, ver 1.23) at org.apache.struts.action.ActionServlet.process(ActionServlet.java:1913) at org.apache.struts.action.ActionServlet.doGet(ActionServlet.java:449) at com.randomnoun.common.webapp.struts.CustomActionServlet.doGet(CustomActionServlet.java:55, ver 1.3) at javax.servlet.http.HttpServlet.service(HttpServlet.java:621) at javax.servlet.http.HttpServlet.service(HttpServlet.java:722) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:224) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:169) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:168) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98) at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:927) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407) at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:987) at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:579) at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:307) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603) at java.lang.Thread.run(Thread.java:722) Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'flarge' in 'field list' at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:525) at com.mysql.jdbc.Util.handleNewInstance(Util.java:406) at com.mysql.jdbc.Util.getInstance(Util.java:381) at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1051) at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3563) at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3495) at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1959) at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2113) at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2687) at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2616) at com.mysql.jdbc.StatementImpl.executeQuery(StatementImpl.java:1464) at org.springframework.jdbc.core.JdbcTemplate$1QueryStatementCallback.doInStatement(JdbcTemplate.java:443) at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:396) ... 29 more
So what you’re getting here is:
- Something that still looks like a stack trace, unlike some other spectacularly undocumented time-saving toolkits.
- Classes within specified packages (and their sub-packages) are highlighted in the stack trace (in the example above, the packages ‘
com.contrived
‘, ‘com.randomnoun
‘, and ‘jsp
‘) - Each class under developer control contains a version identifier:
com.contrived.web.action.MainAction.execute(MainAction.java:33, ver 1.7)
so you can more easily determine exactly what code is running (which is useful for clients which don’t give you access to production machines, yet expect you to be able to fix problems in production).
In the example stacktrace above, I use CVS revision $Id$s, but you could use CI build numbers, maven version numbers, debian package IDs, those meaningless git strings, or whatever you use to track your source code.
My implementation relies on having the following boilerplate code in every class that I have any control over.
1 2
/** A revision marker to be used in exception stack traces. */ public static final String _revision = "$Id$";
which gets expanded out to this when checked into source control:
1 2
/** A revision marker to be used in exception stack traces. */ public static final String _revision = "$Id: ExceptionUtils.java,v 1.8 2012-09-10 21:43:01 knoxg Exp $";
If you decide to use a different identification scheme, then update the
getClassRevision()
method as appropriate. (Or refactor the class to use a ClassRevisionAnnotationFactory, if that’s the sort of thing that keeps you entertained).
I bet you’re just gagging for the code at this stage, so here it is:
and some unit test classes that you’ll never look at:
And a JSP errorPage handler which uses it:
And an example broken JSP:
which looks like this when you try to view it in your application server of choice (click the image below to see the actual page).

What you could also do if you were really keen would be to generate hrefs in the generated HTML stacktrace to (your favourite web-based source control system), but that’s probably not that useful since
- on a production system you probably shouldn’t be showing stack traces since this may lead your end-users to believe that the website/application isn’t being run by a glistening ball of energy in The Cloud somewhere
- the end-user won’t have permissions to your source control system
- on a development system you probably haven’t checked in the code you’re currently debugging anyway
So there you go. If you find the above code useful then more power to you.
Update 2013-09-25: This code is in the com.randomnoun.common:common-public maven artifact:
Update 2021-01-29: and github: