73

By default, Tomcat sends some HTML content back to the client if it encounters something like an HTTP 404. I know that via web.xml an <error-page> can be configured to customize this content.

However, I'd just like for Tomcat to not send anything in terms of response content (I'd still like the status code, of course). Is there any way to easily configure this?

I'm trying to avoid A) explicitly sending empty content on the response stream from my Servlet, and B) configuring custom error pages for a whole bunch of HTTP error statuses in my web.xml.

For some background, I'm developing an HTTP API and am controlling my own response content. So for an HTTP 500, for example, I'm populating some XML content on the response containing error information. For situations like an HTTP 404, the HTTP response status is sufficient for clients, and the content tomcat is sending is unnecessary. If there's a different approach, I'm open to hearing it.

Edit: After continued investigation, I still can't find much in the way of a solution. If someone can definitively say this is not possible, or provide a resource with evidence that it will not work, I'll accept that as an answer and try and work around it.

Rob Hruska
  • 118,520
  • 32
  • 167
  • 192
  • 7
    I'm not overloading the meaning of the codes, I'm using them as they're intended. This is for a REST API - as an example, if someone performs a GET on a certain resource in my API, and I don't find it, I'm setting the response status to 404. If I have some sort of strange error, I set a status of 500 and provide some error content in the response. But I want exclusive control over this content - I don't want Tomcat returning HTML or anything else. If content is to be returned, I want my Servlet to be the one doing it. – Rob Hruska Apr 27 '09 at 18:00
  • 4
    I just also found that Servlet 3 seems to allow for a one-catches-all : http://static.springsource.org/spring/docs/3.2.0.BUILD-SNAPSHOT/reference/html/mvc.html#mvc-ann-customer-servlet-container-error-page – Erich Eichinger Jan 17 '13 at 08:45
  • @ErichEichinger - That's useful information, thanks for passing it along. – Rob Hruska Jan 17 '13 at 13:34

8 Answers8

48

If you do not want tomcat to show an error page, then do not use sendError(...). Instead use setStatus(...).

e.g. if you want to give a 405 response, then you do

response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);      
response.getWriter().println("The method " + request.getMethod() + 
   " is not supported by this service.");

Also remember not to throw any Exceptions from your servlet. Instead catch the Exception and, again, set the statusCode your self.

i.e.

protected void service(HttpServletRequest request,
      HttpServletResponse response) throws IOException {
  try {

    // servlet code here, e.g. super.service(request, response);

  } catch (Exception e) {
    // log the error with a timestamp, show the timestamp to the user
    long now = System.currentTimeMillis();
    log("Exception " + now, e);
    response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    response.getWriter().println("Guru meditation: " + now);
  }
}

of course, if you do not want any content, then just don't write anything to the writer, just set the status.

  • Using `print()` instead of `println()` which causes additional new line char. This especially useful if you are sending the empty string, with `Content-Length: 0` – manikanta Sep 28 '11 at 08:55
  • 3
    I think the real reason why this works is because you've written to the output stream. I believe that if you simply set the status and do not write anything to the response, Tomcat will send the default error response for 405. – matt b Oct 10 '12 at 20:59
  • 1
    Simply setting the status code does not appear to be sufficient to suppress the standard error page (at least, not based on my own experience). – Greg Brown Dec 03 '13 at 17:28
  • 1
    I had to call `flush()` on the writer to ensure my content was not overridden by Tomcat. – Michele Palmia May 27 '15 at 10:12
  • Also, this only works for an empty response, at least with 500 response code. – mjs Nov 11 '15 at 19:39
  • I am getting the same error , can you please have a look at http://stackoverflow.com/questions/38388146/unauthorized-response-is-different – Chetan Jul 15 '16 at 09:45
  • 1
    catching exceptions doesnt seem to work when `ClassNotFoundException` is thrown. error still shows up – user2914191 Apr 12 '18 at 05:48
46

Although this doesn't respond exactly to the "not send anything" statement on the question, and on the wave of Clive Evans' answer, I found out that in tomcat you can make those too much verbose texts go away from error pages without creating a custom ErrorReportValve.

You can accomplish to this customizing ErrorReportValve through the 2 params "showReport" and "showServerInfo" on your "server.xml":

<Valve className="org.apache.catalina.valves.ErrorReportValve" showReport="false" showServerInfo="false" />

Link to official documentation.

Worked for me on tomcat 7.0.55, didn't work for me on tomcat 7.0.47 (I think because of something reported on the following link http://www.mail-archive.com/users@tomcat.apache.org/msg113856.html)

Community
  • 1
  • 1
reallynice
  • 1,289
  • 2
  • 21
  • 41
  • 7
    Working with Tomcat 8. Excellent solution! – eis Oct 08 '15 at 19:06
  • 3
    The Tomcat docs say the element can go within the , or elements in server.xml. For me putting the valve within the element didn't work but putting it inside individual elements did. The docs also say Tomcat routes any non matching host names to the element that is set in the defaultHost attribute of the element. To ensure that URLs to locations outside of your hosts/contexts are not shown the error messages compiled into Tomcat you need to make sure this is added your default . – Night Owl Jun 22 '16 at 19:09
13

The quick, slightly dirty, but easy way of stopping Tomcat from sending any error body is to call setErrorReportValveClass against the tomcat host, with a custom error report valve which overrides report to do nothing. ie:

public class SecureErrorReportValve extends ErrorReportValve {

@Override
protected void report(Request request,Response response,Throwable throwable) {
}

}

and set it with:

  ((StandardHost) tomcat.getHost()).setErrorReportValveClass(yourErrorValveClassName);

If you want to send your message, and just think Tomcat shouldn't mess with it, you want something along the lines of:

@Override
protected void report(final Request request, final Response response, final Throwable throwable) {
    String message = response.getMessage();
    if (message != null) {
        try {
            response.getWriter().print(message);
            response.finishResponse();
        } catch (IOException e) {
        }
    }
}
mckamey
  • 17,359
  • 16
  • 83
  • 116
Clive Evans
  • 658
  • 8
  • 12
  • This worked great! I like having a single place to control this rather than having to wrap every single response. More aspect-oriented. – mckamey Aug 16 '12 at 23:45
  • Indeed a better solution! Thanks – Poni Sep 06 '12 at 18:24
  • 2
    Feel free to point to the part of the spec that says you shouldn't take the Tomcat ErrorReportValve which rewrites your message into a message containing a whole load of information you probably don't want to share with the outside world, and instead, write just your message. I'm intrigued ... – Clive Evans Nov 25 '12 at 11:30
  • 1
    on another note, you can write your own ErrorValve and configure it in the server.xml's element: see http://tomcat.apache.org/tomcat-7.0-doc/config/host.html – Erich Eichinger Jan 16 '13 at 17:46
  • Well, more than a "quick, slightly dirt" answer, looks like the answer that really meets the needs. – reallynice Aug 10 '14 at 19:54
  • And now you application is tomcat dependant. – mjs Nov 11 '15 at 19:35
  • 1
    If you weren't using Tomcat, you wouldn't be suffering from Tomcat leaking your information ... – Clive Evans Nov 12 '15 at 10:32
  • I needed to add `try { response.finishResponse(); } catch (IOException e) { }` to the empty `report()` method to prevent it from producing any output. – John Yeary Aug 22 '18 at 13:22
10

Although it's Servlet spec compliant, for security reasons I don't want tomcat or any other Servlet container to send error details. I struggled with this as well a bit. After searching and trying, the solution can be summed up as:

  1. as others mentioned, don't use sendError(), use setStatus() instead (in Jersey framework you can choose)
  2. frameworks like e.g. Spring Security use sendError() though...
  3. write a Filter that
    a. redirects calls to sendError() to setStatus()
    b. flushes the response at the end to prevent the container from further modifying the response

A little example servlet filter doing this can be found here.

bluish
  • 26,356
  • 27
  • 122
  • 180
Erich Eichinger
  • 1,898
  • 17
  • 15
  • 5
    +1 for pointing out that frameworks have predefined behaviour and providing an example filter, which — as I'm using [Jersey](https://jersey.java.net/) — made me discover a [a specific Jersey property](https://jersey.java.net/apidocs/2.5/jersey/org/glassfish/jersey/server/ServerProperties.html#RESPONSE_SET_STATUS_OVER_SEND_ERROR) – watery Jul 19 '16 at 10:25
  • 1
    Above link posted by @watery is dead as of 2019, correct link now is https://jersey.github.io/apidocs/latest/jersey/org/glassfish/jersey/server/ServerProperties.html#RESPONSE_SET_STATUS_OVER_SEND_ERROR – maxxyme Jul 17 '19 at 08:54
  • Link is dead again, better look at [this answer](https://stackoverflow.com/a/36600434/505893) ;) – bluish Sep 06 '21 at 19:44
9

As Heikki said, setting the status instead of sendError() causes the Tomcat not touch the response entity/body/payload.

If you only want to send the response headers without any entity, like in my case,

response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentLength(0);

does the trick. With Content-Length: 0, the print() will have no effect even if used, like:

response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentLength(0);
response.getWriter().print("this string will be ignored due to the above line");

the client receives something like:

HTTP/1.1 401 Unauthorized
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=utf-8
Content-Length: 0
Date: Wed, 28 Sep 2011 08:59:49 GMT

If you want to send some error message, use the setContentLength() with message length (other than zero) or you can leave it to the server

manikanta
  • 8,100
  • 5
  • 59
  • 66
2

Though this question is a bit old, I ran into this problem too. First of all, Tomcat's behavior is absolutely correct. This is per Servlet Spec. One should not alter Tomcat's behavior against the spec. As Heikki Vesalainen and mrCoder mentioned, use setStatus and setStatus only.

To whom it may concern, I have raised a ticket with Tomcat to improve the docs of sendError.

Michael-O
  • 18,123
  • 6
  • 55
  • 121
  • 1
    Can you clarify what you mean by the behavior being "absolutely correct"? Which behavior, specifically? A link or excerpt from the Servlet specification may help. I couldn't find anything in the 3.0 specification that addressed requirements on the actual content of a response, particularly for error responses. As far as [RFC2616](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html) goes, there aren't any requirements on the entity content for any error statuses. – Rob Hruska Nov 24 '12 at 01:12
  • Yes, of course. This is not bound to the RFC but to the Servlet spec itself. Check Servlet spec 3.0, chapters 10.9.2 and 10.9.3, Oracle's Servlet API JavaDocs for both [`sendError`](http://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletResponse.html#sendError%28int%29) methods and my question to the [Tomcat Users Mailinglist](http://www.mail-archive.com/users@tomcat.apache.org/msg102755.html). – Michael-O Nov 24 '12 at 11:20
2

Configure <error-page> Elements in web.xml

Edit $CATALINA_HOME/conf/web.xml, add at the end the following <error-page>, save and restart tomcat

<web-app>

...
...
...

    <error-page>
        <error-code>404</error-code>
        <location>/404.html</location>
    </error-page>

    <error-page>
        <error-code>500</error-code>
        <location>/500.html</location>
    </error-page>

    <error-page>
        <error-code>400</error-code>
        <location>/400.html</location>
    </error-page>

</web-app>
  • It works great as I expect even though I didn't actually created a valid routes for those specified location values (e.g. /400.html)

before

enter image description here

after

enter image description here

Jossef Harush Kadouri
  • 32,361
  • 10
  • 130
  • 129
  • 1
    You missed the point where the OP said he's _trying to avoid [..] configuring custom error pages for a whole bunch of HTTP error statuses in [his] web.xml_. – maxxyme Jul 17 '19 at 08:56
  • Agree. Yet its helpful for those who google their way for examples. This question have good SEO rating. – Jossef Harush Kadouri Jul 17 '19 at 09:25
1

Why not just configure the <error-page> element with an empty HTML page?

matt b
  • 138,234
  • 66
  • 282
  • 345
  • 2
    I mentioned in my question that I would like to avoid configuring error pages (even if they're empty) for a bunch of statuses. If it's my only option then I guess I'll have to - but I'm looking for alternatives. – Rob Hruska Apr 27 '09 at 18:02