Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I am developing a REST service using SpringMVC, where I have @RequestMapping at class and method level.

This application is currently configured to return error-page jsp configured in web.xml.

<error-page>
    <error-code>404</error-code>
    <location>/resourceNotFound</location>
</error-page>

I however want to return custom JSON instead of this error page.

I am able to handle exception and return json for other exceptions, by writing this in controller, but not sure how and where to write the logic to return JSON when the url does not exist at all.

    @ExceptionHandler(TypeMismatchException.class)
        @ResponseStatus(value=HttpStatus.NOT_FOUND)
        @ResponseBody
        public ResponseEntity<String> handleTypeMismatchException(HttpServletRequest req, TypeMismatchException ex) {

            HttpHeaders headers = new HttpHeaders();
            headers.add("Content-Type", "application/json; charset=utf-8");
            Locale locale = LocaleContextHolder.getLocale();
            String errorMessage = messageSource.getMessage("error.patient.bad.request", null, locale);

            errorMessage += ex.getValue();
            String errorURL = req.getRequestURL().toString();

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

        }

I tried @ControllerAdvice, it works for other exception scenarios, but not when mapping is not avaialble,

@ControllerAdvice
public class RestExceptionProcessor {

    @Autowired
    private MessageSource messageSource;

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

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

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

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

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

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


}
like image 207
Himalay Majumdar Avatar asked Mar 03 '14 21:03

Himalay Majumdar


1 Answers

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"}
like image 70
Himalay Majumdar Avatar answered Sep 24 '22 21:09

Himalay Majumdar