When deploying Tomcat in AWS or any production environment, exposing stack traces in error pages creates security vulnerabilities by revealing:
- Internal package structures
- Class/method implementation details
- Potential attack vectors
- Sensitive framework information
Tomcat provides multiple ways to suppress stack traces in error responses:
<!-- Option 1: In server.xml -->
<Valve className="org.apache.catalina.valves.ErrorReportValve"
showReport="false"
showServerInfo="false"/>
Alternatively, create a custom error page:
<!-- In web.xml -->
<error-page>
<exception-type>java.lang.Throwable</exception-type>
<location>/error.html</location>
</error-page>
For granular control, extend ErrorReportValve:
public class SecureErrorValve extends ErrorReportValve {
@Override
protected void report(Request request, Response response, Throwable throwable) {
response.setStatus(getStatus(request, response, throwable));
// Write minimal error info without stack trace
response.getWriter().write("An error occurred");
}
}
After configuration, test with:
- Trigger an application exception
- Verify HTTP 503 response
- Confirm absence of stack traces
- Check server logs for detailed errors
- Combine with proper logging (Log4j/SLF4J)
- Implement centralized error monitoring
- Use HTTPS for all error responses
- Consider HTTP security headers
When running Tomcat in production (especially on cloud platforms like AWS), you might notice that unhandled exceptions result in error pages displaying full Java stack traces. This exposes internal implementation details, including:
- Class names and package structures
- Method invocation sequences
- Line numbers in source files
While stack traces are invaluable during development, they become a security risk in production because:
- They reveal application architecture to potential attackers
- May contain sensitive information in exception messages
- Violate the principle of least information disclosure
The most effective approach is to configure custom error pages in web.xml:
<error-page>
<error-code>500</error-code>
<location>/error/500.html</location>
</error-page>
<error-page>
<exception-type>java.lang.Throwable</exception-type>
<location>/error/500.html</location>
</error-page>
For more control, implement a custom ErrorReportValve:
public class ProductionErrorReportValve extends ErrorReportValve {
@Override
protected void report(Request request, Response response, Throwable throwable) {
// Only show error code, no stack traces
response.sendError(response.getStatus());
}
}
Then configure it in server.xml:
<Valve className="com.yourpackage.ProductionErrorReportValve"/>
Combine this with security headers in your web.xml:
<filter>
<filter-name>httpHeaderSecurity</filter-name>
<filter-class>org.apache.catalina.filters.HttpHeaderSecurityFilter</filter-class>
<init-param>
<param-name>antiClickJackingOption</param-name>
<param-value>SAMEORIGIN</param-value>
</init-param>
</filter>
After implementation, test by:
- Triggering a 500 error intentionally
- Checking response contains only HTTP status code
- Verifying no stack trace appears in HTML source