Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring MVC, Migrate @ExceptionHandler to HandlerExceptionResolver for a RESTful service

Background

I have an error message class:

@XmlRootElement
public class ErrorMessage {
    private String message;

    public ErrorMessage() {
    }

    public ErrorMessage(String message) {
        this.message = message;
    }

    public String getError() {
        return message;
    }

    public void setError(String message) {
        this.message = message;
    }
}

This class has been assigned as a return value to an @ExceptionHandler in my Spring MVC REST controller:

@ExceptionHandler
@ResponseStatus(HttpStatus.NOT_FOUND)
@ResponseBody
ErrorMessage handleException(RuntimeException e) {
    return new ErrorMessage("something went wrong");
}

Whenever the client triggers a RuntimeException after issuing a request with application/json as a Accept header, it receives a response with the correct status code and a matching JSON body:

{"error":"something went wrong"}

Alternatively, an XML body is received if the Accept header is application/xml:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<errorMessage><error>something went wrong</error></errorMessage>

Problem

Now, I would like to generify this solution by implementing a HandlerExceptionResolver instead so I don't have to copy / paste the @ExceptionHandler to every controller (or create a common "parent controller" that the other controllers can extend).

However, the AbstractHandlerExceptionResolver.doResolveException() method returns a ModelAndView and not my propriety ErrorMessage, so I tried the following:

public class RuntimeExceptionHandlerExceptionResolver extends AbstractHandlerExceptionResolver {

    @Override
    protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        if (ex instanceof RuntimeException) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            ModelAndView mav = new ModelAndView();
            mav.addObject("error", "something went wrong");
            return mav;
        }
        return null;
    }
}

When debugging, I can see that the mav.addObject() method is called. The response on the client side has the expected status code, but the content type is text/html with html in the body, rather than the JSON or XLM content that was specified by the Accept header in the original request.

(Side note, the actual exception, response code and text message in the example above is not important, they just serve as simple example.)

Spring version: 3.1.1.RELEASE

like image 326
matsev Avatar asked Jun 04 '12 17:06

matsev


People also ask

How do you handle exceptions in Spring MVC?

Spring MVC Framework provides following ways to help us achieving robust exception handling. Controller Based - We can define exception handler methods in our controller classes. All we need is to annotate these methods with @ExceptionHandler annotation. This annotation takes Exception class as argument.

How does Spring handle global exception?

Spring MVC provides exception handling for your web application to make sure you are sending your own exception page instead of the server-generated exception to the user. The @ExceptionHandler annotation is used to detect certain runtime exceptions and send responses according to the exception.

How do I send a custom error message in REST API Spring boot?

The most basic way of returning an error message from a REST API is to use the @ResponseStatus annotation. We can add the error message in the annotation's reason field. Although we can only return a generic error message, we can specify exception-specific error messages.


1 Answers

To get @ExceptionHandlers to use content negotiation, the AnnotationMethodHandlerExceptionResolver has a setMessageConverters() method which must be provided with the message converters, e.g.:

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver">
    <property name="messageConverters">
        <list>
            <ref bean="xmlConverter"/>
            <ref bean="jsonConverter"/>
        </list>
    </property>
</bean>

But since you're using a custom method, you'll probably have to implement this functionality yourself. The simplest way is probably to Ctrl-C Ctrl-V from the AnnotationMethodHandlerExceptionResolver source, specifically the handleResponseBody() method.

like image 200
beerbajay Avatar answered Oct 07 '22 09:10

beerbajay