Spring MVC beat me down with a 404

Before I get started on this, here is the solution I chose for setting up a 404 error trap in a Spring Web MVC (Spring 3.2). It may seem old school, but after hours of research and testing this is what works the best for my application.

Solution

In web.xml add something like this, you r location could be an html page but mine is an error controller endpoint:

<error-page>

        <error-code>404</error-code>

        <location>/error/404</location>

</error-page>

My error controller looks like this, in my case I return an error object that has a status and message property that gets deserialized to json:

@Api(description = “Error Controller to catch errors not covered by ControllerAdvice.”, value = “/error”)

@Controller

@RequestMapping(value = “/error”, produces = MediaType.APPLICATION_JSON_VALUE)

public
class ErrorController {

@RequestMapping(value=“/404”)


@ResponseStatus(HttpStatus.NOT_FOUND)


@ApiOperation(value = “404 Error Handler”)


public
@ResponseBody ControllerResponse unmappedRequest(HttpServletResponse response) throws IOException {

    return
new ControllerResponse.Builder().status(ControllerResponseEnum.error).message(HttpStatus.NOT_FOUND.getReasonPhrase()).build();

}

}

ControllerReponse is just a simple json pojo

@JsonInclude(Include.NON_NULL)

public
class ControllerResponse {

@JsonProperty

@JsonFormat(shape = JsonFormat.Shape.STRING)

private ControllerResponseEnum status;


@JsonProperty

private String data;


@JsonProperty

private String meta;


@JsonProperty

private String message;

…. Plus getters/setters etc. . . .

ControllerResponseEnum simply lists the values suggested in the JSend spec: success, fail, or error.

public
enum ControllerResponseEnum {

    success, fail, error

}

Things That Didn’t Work For Me – 406’s everywhere

Special controller mapped to /** didn’t work

At first I thought I could just create an error controller and catch everything coming in at /** by adding a request mapping and throw the error from there, centralizing my error responses. This works great like this . . . (but . . . )


@RequestMapping(value=“/**”)

@ResponseStatus(HttpStatus.NOT_FOUND)

@ApiOperation(value = “404 Error Handler”)

public
@ResponseBody ControllerResponse unmappedRequest(HttpServletResponse response) throws IOException {

The good thing is – it intercepts EVERYTHING. The bad thing is – it intercepts EVERYTHING. So I ran into problems with my static resource files. There’s a schema and some ui on the path needed for the ReST application:

    <mvc:resources mapping=“/static/**” location=“/static/” />

But the “/**” controller wreaks havoc. I tried everything under the sou to get this to work: writing redirects in the error controller for instance, some fancy web.xml default servlet mappings. Nothing. It throws out 406 errors (NOT ACCEPTABLE error).

Interceptors require too much code to maintain, comparatively to the solution I needed

Even tried configuring an intercept filter and handle the code there, but I started to write waaaaaay tooo much.

    <mvc:interceptors>

        <mvc:interceptor>

            <mvc:mapping path=“/**” />

            <bean class=“com.ist.common.error.StaticContentInterceptor”/>

        </mvc:interceptor>

    </mvc:interceptors>

Or try it with java config file:

@Configuration

@EnableWebMvc

public
class StaticContentConfig extends WebMvcConfigurerAdapter {

//@formatter:off

@Override

public
void addInterceptors(InterceptorRegistry registry) {

    registry.addInterceptor(new
StaticContentInterceptor())

        .addPathPatterns(“/**”) }

//@formatter:on

}

Nope.

MIME type mappings – might as well just do <error> config if you are messing with web.xml:

How about mounting every freakin static mime type . . . .

<servlet-mapping>

<servlet-name>MyApp</servlet-name>

<url-pattern>/</url-pattern>

</servlet-mapping>

<!– The ‘dynamic’ content –>

<servlet-mapping>

<servlet-name>default</servlet-name>

<url-pattern>*.css</url-pattern>

</servlet-mapping>

<servlet-mapping>

<servlet-name>default</servlet-name>

<url-pattern>*.js</url-pattern>

</servlet-mapping>

<servlet-mapping>

<servlet-name>default</servlet-name>

<url-pattern>*.jpg</url-pattern>

</servlet-mapping>
<!– The ‘static’ content –>

The 404 isn’t a Spring MVC issue. The error is scoped outside of the framework. By the time you get there, you’ve passed up a lot of other functionality. Here are some key conversations about the issue that had me settle on what I put in:

https://jira.springsource.org/browse/SPR-8837

http://stackoverflow.com/questions/11792231/spring-mvc-catch-http-errors-400-404

So I guess what burned me is that a 404 is a basic error of doing web applications, and I just don’t like having more than one way to do things if I can help it. I handle 500 errors via the framework, so why not 404’s? The solution makes sense for now, as the simplest. Of course you could write a log of code in the interceptor or controller /** method, but that (IMHO) defeats the purpose of convention over a coded configuration. Since I was left to configuration, I just chose the simplest path.

Comments are closed.