Monday, November 19, 2007

Print a stacktrace in your JSP

Update 4/03/2009 5:00:37 PM. Added notes about catching Throwable and not to leave this code in by mistake.

Sometimes I want to capture an error that destroys my JSP and display it right there. You can do this with JSTL and log4j as well, but this is the plain old vanilla JSP way.

<% try { %>
<p>You will see this.</p>
<% if (true) throw new IllegalStateException("Naughty!"); %>
<p>You will not see this.</p>
<%
} catch (Exception exception) {
out.println("<pre style=\"color: red\">");
exception.printStackTrace(new java.io.PrintWriter(out));
out.println("</pre>");
}
%>

The output is as per below.

You will see this.

java.lang.IllegalStateException: Naughty!
at org.apache.jsp.default_jsp._jspService(default_jsp.java:62)
at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:98)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:331)
at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:329)
at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:265)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:269)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:188)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:213)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:174)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:117)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:108)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:151)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:874)
at org.apache.coyote.http11.Http11BaseProtocol$Http11ConnectionHandler.processConnection(Http11BaseProtocol.java:665)
at org.apache.tomcat.util.net.PoolTcpEndpoint.processSocket(PoolTcpEndpoint.java:528)
at org.apache.tomcat.util.net.LeaderFollowerWorkerThread.runIt(LeaderFollowerWorkerThread.java:81)
at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:689)
at java.lang.Thread.run(Thread.java:595)

Warning: this is just a measure for quick diagnosis and trouble shooting during development. Never leave this stuff in your code unless you have very good reason. A user should only ever see a friendly message like "Operation failed. Please try again or contact support". They should never see stack traces; they can't do anything with that information and it is confusing (and thus un-professional). This output should go to your logs instead.

I have worked on applications before where I needed to resort to this; something else in the infrastructure or framework can be swallowing the exceptions, or it is unreasonably hard to get the logs quickly (and easier to edit JSP!).

For those really nasty errors, catching java.lang.Exception isn't enough. For example, a java.lang.NoClassDefFoundError isn't an Exception and won't be caught by this. It's a java.lang.Error (Exceptions's only sibling). To catch any Exception or Error, change the catch statement to catch (Throwable exception).

Double Warning: be doubly sure never to leave a catch Throwable in your code. In all cases it means something drastic is wrong - something that your code absolutely cannot recover from.

1 comment:

RobertMarkBram said...

Or, using JSTL to print out an error for users to see i.e. can't use JSTL to print out a stacktrace:

<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>

<c:catch var="exception">

</c:catch>
<c:if test="${not empty exception}">
<pre style="color: red;">
Error: <b><c:out value="${exception.message}" /></b>
This indicates there was a problem doing XXX.
Please press your browser's back button and try again.
If the problem persists, contact Support.
</pre>
</c:if>