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)
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.
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.
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?
It seems your basic problem is that you want to centralize your error handling but without using a mechanism that causes Tomcat to log the errors as SEVERE
?
Since you control all the servlets AFAICT from your question would it not make more sense to define an abstract base servlet that defines all the error handling logic and then just have the rest of your servlets derive from this class?
So you have an abstract base servlet:
public abstract class MyServletBase extends HttpServlet {
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) {
try {
doGetInternal(req, resp);
} catch (RuntimeException e) {
handleError(e, req, resp);
}
}
protected void handleError(RuntimeException e, HttpServletRequest req, HttpServletResponse resp) {
// Error handling logic goes here
}
protected void doGetInternal(HttpServletRequest req, HttpServletResponse resp);
}
And then your actual servlet:
public class MyServlet extends MyServletBase {
@Override
protected void doGetInternal(HttpServletRequest req, HttpServlet resp) {
// Actual servlet logic here
}
}
This is a rough sketch off the top of my head with no reference to the Javadoc so may not have the method signatures perfect but hopefully you get the idea.
It also has the advantage that if you ever need to add extra error handling logic to a derived servlet and you don't want to change the base servlet for whatever reason you can just override the handleError()
method which is not something you can so easily do when using Tomcat's exception handler mechanism