Learn how to efficiently customize 404 error responses for pre-context path URLs in Spring Boot applications. Ensure a seamless user experience and consistent error handling for your API.
1. Introduction
In our previous article, “Enhancing API Error Responses: How to Customize 404 Not Found Response in Spring Boot,” we discussed how to customize the 404 error response for invalid URLs in our Spring Boot application. However, we encountered an issue where invalid URLs before the context path were returning a default 404 HTML error response from Tomcat. In this extended article, we’ll delve into how to override this behavior and ensure consistent error handling for all invalid URLs.
2. The Problem: Inconsistent 404 Responses
When dealing with invalid URLs, Spring Boot’s default error handling can be inconsistent. Invalid URLs after the context path can be managed using a global error controller wherein we can handle the “NoHandlerFoundException” and customize the error responses as per our requirement, but pre-context path errors often result in default Tomcat 404 HTML responses, disrupting API consistency.
Let’s break it down with detailed scenarios:
Let’s first understand our Spring Boot app, where we’ve configured the context path as /greetings
. Additionally, we have one endpoint, /hello
, which sends a simple “Hello, World!” message to the user.
Scenario 1: Invalid URLs After Context Path “greetings”
If a user sends a request with an invalid URL after this context path, such as localhost:8080/greetings/invalid
, Spring Boot can handle this within a global error controller. It does so by catching the “NoHandlerFoundException,” which means Spring Boot couldn’t find a suitable handler for the requested URL. This allows us to customize the error response and show a user-friendly message instead of the default error response.
For a more in-depth guide on configuring this error handling for invalid URLs after the context path, you can refer to our dedicated article “Enhancing API Error Responses: How to Customize 404 Not Found Response in Spring Boot“.
Output:
Scenario 2: Requests without Context Path
Now, if a user sends a request without including the context path, such as localhost:8080/invalid
, Spring Boot will treat this as a request without a specific context. In such cases, Tomcat, which is the underlying server for Spring Boot, will generate a default 404 HTML response. This default response is less informative and may not provide the user with helpful guidance on what went wrong.
Output:
3. The Solution: Custom Tomcat Error Valve
To tackle the issue of 404 errors for URLs without a context path or invalid URLs before the context path, we can implement a custom Tomcat error valve. By leveraging a Tomcat customizer in Spring Boot, we can override the default error handling behavior, ensuring a consistent and user-friendly error handling experience.
import org.apache.catalina.Container;
import org.apache.catalina.core.StandardHost;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;
@Component
public class TomcatCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
@Override
public void customize(TomcatServletWebServerFactory factory) {
factory.addContextCustomizers((context) -> {
Container parent = context.getParent();
if (parent instanceof StandardHost standardHost) {
standardHost.setErrorReportValveClass(CustomTomcatErrorValve.class.getName());
}
});
}
}
TomcatCustomizer.javaIn the example above, we are customizing error handling in a Tomcat servlet web server. It uses the WebServerFactoryCustomizer
interface to customize the configuration of the Tomcat servlet web server factory. Specifically, it adds customizations to the Tomcat context by setting a custom error valve class (CustomTomcatErrorValve
) for specific hosts. This custom error valve allows for more controlled and tailored handling of error reports within the Tomcat server, enhancing the application’s error handling capabilities.
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.valves.ErrorReportValve;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import java.io.IOException;
import java.io.Writer;
public class CustomTomcatErrorValve extends ErrorReportValve {
private static final Logger logger = LoggerFactory.getLogger(CustomTomcatErrorValve.class);
@Override
protected void report(Request request, Response response, Throwable throwable) {
logger.error("Tomcat failed to prepare the request for spring", throwable);
if (!response.setErrorReported())
return;
HttpStatus status = HttpStatus.valueOf(response.getStatus());
try {
Writer writer = response.getReporter();
if (status == HttpStatus.NOT_FOUND) {
logger.error("Customizing response for 404 error using CustomTomcatErrorValve", throwable);
String errorJson = String.format("""
{
"message": "Resource not found",
"code": %d
}""", status.value());
writer.write(errorJson);
response.setContentType("application/json");
response.finishResponse();
}
} catch (IOException exception) {
logger.error("IOException encountered.", exception);
}
}
}
CustomTomcatErrorValve.javaThe above code defines a custom error valve, CustomTomcatErrorValve
, extending from ErrorReportValve
. The valve handles error reports within the Tomcat server environment. When an error occurs, such as a 404 Not Found error, it logs the error, sets the response content type to JSON, and sends a custom JSON error message back to the client. The error message includes a descriptive “message” field (“Resource not found”) and an error “code” field corresponding to the HTTP status code. This customization improves error handling by providing more informative and structured error responses to clients.
Output:
4. Customizing 404 Error Response with HTML Content
If you want to configure HTML error messages using the CustomTomcatErrorValve
, you can use the provided code snippet. This code snippet demonstrates how to write HTML content in the response writer, enabling developers to create personalized and informative error responses for handling 404 errors.
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.valves.ErrorReportValve;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import java.io.IOException;
import java.io.Writer;
public class CustomTomcatErrorValve extends ErrorReportValve {
private static final Logger logger = LoggerFactory.getLogger(CustomTomcatErrorValve.class);
@Override
protected void report(Request request, Response response, Throwable throwable) {
logger.error("Tomcat failed to prepare the request for spring", throwable);
if (!response.setErrorReported())
return;
HttpStatus status = HttpStatus.valueOf(response.getStatus());
try {
Writer writer = response.getReporter();
if (status == HttpStatus.NOT_FOUND) {
logger.error("Customizing response for 404 error using CustomTomcatErrorValve", throwable);
String errorHtml = """
<!DOCTYPE html>
<html>
<head>
<meta charset="ISO-8859-1">
<title>404 Error</title>
</head>
<body>
<h3>404 - Resource not found</h3>
</body>
</html>
""";
writer.write(errorHtml);
response.setContentType("text/html");
response.finishResponse();
}
} catch (IOException exception) {
logger.error("IOException encountered.", exception);
}
}
}
CustomTomcatErrorValve.javaOutput:
5. Source Code
The complete source code of the above examples can be found here.
6. Conclusion
In conclusion, we’ve gained valuable insights into efficiently customizing 404 error responses for pre-context path URLs in Spring Boot applications. By leveraging the capabilities of the ErrorReportValve class, developers can ensure a seamless user experience and consistent error handling for their APIs. This customization empowers developers to tailor error messages specifically for pre-context path URLs, enhancing the overall usability and reliability of their Spring Boot applications.
7. Learn More
Interested in learning more?
Check out our blog on Spring Boot Virtual Threads Explained: Easy Configuration & Examples
Add a Comment