Spring MVC REST Handing Bad Url (404) by returning JSON

If you are using Spring Boot, set BOTH of these two properties:

spring.resources.add-mappings=false
spring.mvc.throw-exception-if-no-handler-found=true

Now your @ControllerAdvice annotated class can handle the "NoHandlerFoundException", as below.

@ControllerAdvice
@RequestMapping(produces = "application/json")
@ResponseBody
public class RestControllerAdvice {

    @ExceptionHandler(NoHandlerFoundException.class)
    public ResponseEntity<Map<String, Object>> unhandledPath(final NoHandlerFoundException e) {
        Map<String, Object> errorInfo = new LinkedHashMap<>();
        errorInfo.put("timestamp", new Date());
        errorInfo.put("httpCode", HttpStatus.NOT_FOUND.value());
        errorInfo.put("httpStatus", HttpStatus.NOT_FOUND.getReasonPhrase());
        errorInfo.put("errorMessage", e.getMessage());
        return new ResponseEntity<Map<String, Object>>(errorInfo, HttpStatus.NOT_FOUND);
    }

}

note it is not sufficient to only specify this property:

spring.mvc.throw-exception-if-no-handler-found=true

, as by default Spring maps unknown urls to /**, so there really never is "no handler found".

To disable the unknown url mapping to /**, you need

spring.resources.add-mappings=false ,

which is why the two properties together produce the desired behavior.


After digging around DispatcherServlet and HttpServletBean.init() in SpringFramework I see that its possible in Spring 4.

org.springframework.web.servlet.DispatcherServlet

/** Throw a NoHandlerFoundException if no Handler was found to process this request? **/
private boolean throwExceptionIfNoHandlerFound = false;

protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
    if (pageNotFoundLogger.isWarnEnabled()) {
        String requestUri = urlPathHelper.getRequestUri(request);
        pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + requestUri +
                "] in DispatcherServlet with name '" + getServletName() + "'");
    }
    if(throwExceptionIfNoHandlerFound) {
        ServletServerHttpRequest req = new ServletServerHttpRequest(request);
        throw new NoHandlerFoundException(req.getMethod().name(),
                req.getServletRequest().getRequestURI(),req.getHeaders());
    } else {
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
    }
}

throwExceptionIfNoHandlerFound is false by default and we should enable that in web.xml

<servlet>
    <servlet-name>appServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>throwExceptionIfNoHandlerFound</param-name>
            <param-value>true</param-value>
        </init-param>
    <load-on-startup>1</load-on-startup>
    <async-supported>true</async-supported>
</servlet>

And then you can catch it in a class annotated with @ControllerAdvice using this method.

@ExceptionHandler(NoHandlerFoundException.class)
@ResponseStatus(value=HttpStatus.NOT_FOUND)
@ResponseBody
public ResponseEntity<String> requestHandlingNoHandlerFound(HttpServletRequest req, NoHandlerFoundException ex) {
    Locale locale = LocaleContextHolder.getLocale();
    String errorMessage = messageSource.getMessage("error.bad.url", null, locale);

    String errorURL = req.getRequestURL().toString();

    ErrorInfo errorInfo = new ErrorInfo(errorURL, errorMessage);
    return new ResponseEntity<String>(errorInfo.toJson(), HttpStatus.BAD_REQUEST);
}

Which allows me to return JSON response for bad URLs for which no mapping exist, instead of redirecting to a JSP page :)

{"message":"URL does not exist","url":"http://localhost:8080/service/patientssd"}