Problem description
Tomcat is logging a SEVERE message including a stacktrace when my HttpServlet is throwing a ServletException, although it is properly re-directed to another HttpServlet in the web.xml.
Tomcat logs the following message with stacktrace:
21-Mar-2015 15:24:57.521 SEVERE [http-nio-8080-exec-28] org.apache.catalina.core.StandardWrapperValve.invoke Servlet.service() for servlet [MyHttpServlet] in context with path [/HttpServletExceptionHandler] threw exception [CustomException] with root cause CustomException
at MyHttpServlet.doGet(MyHttpServlet.java:20)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:618)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:501)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:610)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:516)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1086)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:659)
at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:223)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1558)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1515)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
What did I do?
First, MyHttpServlet throws a ServletException wrapping a CustomException (subclass of Exception) in it's doGet() method:
public class MyHttpServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
throw new ServletException(new CustomException());
}
}
Then, the thrown CustomException is re-directed to MyServletExceptionHandler (mapped to location '/MyServletExceptionHandler'. This re-direction is defined in the following manner in the web.xml:
<error-page>
<exception-type>CustomException</exception-type>
<location>/MyServletExceptionHandler</location>
</error-page>
Finally, MyServletExceptionHandler receives the thrown exception and prints it:
public class MyServletExceptionHandler extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
final Throwable throwable = (Throwable) req.getAttribute("javax.servlet.error.exception");
System.out.println("MyServletExceptionHandler caught Throwable: " + throwable.toString());
}
}
This results in the expected 'MyServletExceptionHandler caught Throwable: CustomException' print so this does work, but somehow Tomcat also logs the SEVERE message mentioned above, including that stacktrace. This messes up my logging.
Why do I want it this way?
According to Java Beat's OCEJWCD 6 Mock Exam – 4 the above mentioned method is the proper way to deal with Exception handling in Servlets. Question 29 states (spoiler alert: bold are correct answers):
Which of the following is a sensible way of sending an error page to the client in case of a business exception that extends from java.lang.Exception?
- Catch the exception and use RequestDispatcher to forward the request to the error page
- Don’t catch the exception and define the ‘exception to error-page’ mapping in web.xml
- Catch the exception, wrap it into ServletException and define the ‘business exception to error-page’ mapping in web.xml
- Catch the exception, wrap it into ServletException, and define the ‘ServletException to error-page’ mapping in web.xml
- Don’t do anything, the servlet container will automatically send a default error page
The third answer (which is marked as correct) clearly states that my way of re-directing the exceptions is a sensible solution.
Further discussion material
I found the following quote on this page (from 10-2-2012 by Tom Holloway at CodeRanch.com)
Actually, a ServletException has nowhere to go uphill in a webapp, and therefore having it appear on the master console isn't really that unreasonable, because it indicates that the application isn't handling the problem itself.
In fact, the Javadocs say this about the ServletException constructor:
"Constructs a new servlet exception with the specified message. The message can be written to the server log and/or displayed for the user."
Note that it explicitly says server log.
The server can get involved in a number of ways here. First, you should be able to define a general exception handler in web.xml to permit the app to deal with the exception, where that handler can not only log to the application log, but can determine what, if any, recovery action should be taken (something that the more generic server code cannot do). Secondly, you can define a custom error page, in which case Tomcat will catch the ServletException and dispatch that page. Note, however that the operative word is page. Like login screens, these pages are invoked directly from Tomcat, and therefore cannot be routed through servlets. In other words, use HTML or JSP, not Struts or JSF.
Bottom line, though, is that throwing ServletExceptions is a sign of bad application design. It means that someone was too lazy or too rushed to properly deal with a problem. Compared to that, the location where the error is logged is of secondary importance.
This statement makes me question the Java Beat's OCEJWCD Mock Exam (mentioned above) and my own solution as good practice. Do you think business exceptions should be handled by another Servlet? And if so, do you think that the Servlet Container (Tomcat) should log the stacktrace of these Exceptions or not? If not, what would then be the best practice?
Final remarks
- Throwing RuntimeExceptions instead of ServletExceptions results in the same SEVERE log.
- A working example of the problem is provided via this Bitbucket repository.