4

Using Spring, I have a SimpleMappingExceptionResolver that catches any unexpected exceptions in my application in the resolveException method. In the method, I return a ModelAndView that gives error message text back to the HTTP client. Here's the code:

public class UnExpectedExceptionResolver extends SimpleMappingExceptionResolver {

private Log logger = LogFactory.getLog(this.getClass().getName());
private ResourceBundleMessageSource messageSource;

@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) {

    // let the end user know that an error occurred
    Locale locale = RequestContextUtils.getLocale(request);
    String messageText = messageSource.getMessage("systemError", null, locale);
    Message message = new Message(messageText, MessageType.ERROR);
    ModelAndView mav = new ModelAndView();
    mav.setView(new MappingJacksonJsonView());
    mav.addObject("message", message);
    return mav;
}

As such, the response is returned with a HTTP status code of 200 with response text being the message (JSON). Unfortunately, the client thinks it's a valid response due to the 200 code and tries to process it as such. I tried setting the HTTP status code to 500 as follows:

 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Server Error");

right before the

 return mav;

statement. Unfortunately, this returns a HTML page indicating a internal error instead of my JSON message. How can I return the JSON message and still indicate a server error (or some type of error) to the client? Specifically, I expect the client's error function in the AJAX call to be invoked and still have the message data sent back to the client. FYI - I'm using jQuery on the client side.

James
  • 2,876
  • 18
  • 72
  • 116
  • 1
    Which version of Spring are you using? That is essential: Spring 3.1+ seems to support [`@ResponseBody`](http://stackoverflow.com/questions/5097134/spring-exceptionhandler-does-not-work-with-responsebody) , if you want just the response status you can throw an exception decorated with `@ResponseStatus`. If you are lucky and using Spring 3.2 you can return [ResponseEntity](http://stackoverflow.com/questions/14247126/spring-mvc-controller-null-return-handler). – Boris Treukhov Jan 23 '13 at 21:36
  • I am using Spring 3.0.5. It supports @ResponseBody. – James Jan 23 '13 at 22:46
  • yes, but not in '@ExceptionHandler' annotated methods. Imho you should consider switching to Spring 3.2. Were there any good techniques, then Spring team wouldn't introduce new exception handlers with controller advices. – Boris Treukhov Jan 23 '13 at 23:00
  • Thanks for the suggestion. I have made the switch to Spring 3.2. I looked at your "ResponseEntity" and the "@ResponseBody" link (as well as the answers below). It seems that I have to put the exception handler methods in each Controller. How can I use the ResponseEntity such that the exception handler exists in one class and catches any unexpected exceptions from all controllers as SimpleMappingExceptionResolver does? If you could provide an answer using Spring 3.2 that would be much appreciated. – James Jan 25 '13 at 15:41

4 Answers4

10

I don't know how exactly you are making the requests to the server. But this is how I would do it.

@ExceptionHandler(Exception.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR, reason = "your message")
public void handleException(IllegalStateException ex, HttpServletResponse response) throws IOException
{
}

In in the client side

$.ajax({
type : "POST",
url : urlString,
data : params,
dataType : 'json',
success : function(data) {
    //  do something}
error: function (xhr, ajaxOptions, thrownError) {
    alert(xhr.status); //This will be 500
    alert(xhr.responseText); // your message
    //do stuff
  }
shazinltc
  • 3,616
  • 7
  • 34
  • 49
  • Thanks for the answer. I'm making the request via jQuery ajax as you're showing in your answer. Where (which class) would I put the handleException method? – James Jan 25 '13 at 15:44
  • Depends, If you have only a single case, you may put this in the controller to which the AJAX call is made. If not write a BaseController which all other controllers extend. So any exception would go to that method and return a 500 error code. – shazinltc Jan 26 '13 at 05:20
  • what does params mean? – user3369592 Jul 27 '16 at 02:55
  • @user3369592: it is a json representation of the value you want to send – shazinltc Aug 23 '16 at 10:10
6

In Spring 3.2 you can put your exception handler inside a @ControllerAdvice annotated class.

From Spring 3.2 documenation

Classes annotated with @ControllerAdvice can contain @ExceptionHandler, @InitBinder, and @ModelAttribute methods and those will apply to @RequestMapping methods across controller hierarchies as opposed to the controller hierarchy within which they are declared. @ControllerAdvice is a component annotation allowing implementation classes to be auto-detected through classpath scanning.

So if your controllers are picked up by autoscanning @Controller annotated classes, @ControllerAdvice should also work(if you scan @Controller classes with an explicit annotation expression, you may need to register this bean separately).

@ControllerAdvice
public class AppControllerAdvice{
        @ExceptionHandler(Throwable.class)
        ResponseEntity<String> customHandler(Exception ex) {
        return new ResponseEntity<String>(
                "Custom user message",
                HttpStatus.INTERNAL_SERVER_ERROR);

} 

Please note that the text is a part of the returned entity and not an HTTP reason phrase.

Boris Treukhov
  • 17,493
  • 9
  • 70
  • 91
  • I like this approach the best. I have a collection of Exception Handlers for various exceptions, and inside of my controllers I throw real java exceptions. The advice catches them and translates them into the appropriate HTTP status code, and message. – Jazzepi Mar 24 '14 at 20:31
4

Here is how I did it.

public class CustomExceptionResolver extends AbstractHandlerExceptionResolver {

    private static final Logger logger = Logger.getLogger(CustomExceptionResolver.class);

    protected ModelAndView doResolveException(HttpServletRequest request,
                                              HttpServletResponse response,
                                              Object handler,
                                              Exception ex) {
        try {
            response.reset();
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            response.setCharacterEncoding("UTF-8");
            response.setContentType("text/json");
            MappingJacksonJsonView view = new MappingJacksonJsonView();
            Map<String, String> asd = new HashMap<String, String>();
            asd.put("message", ex.getMessage());
            view.setAttributesMap(asd);
            return new ModelAndView(view);
        } catch (Exception e) {
            logger.error("send back error status and message : " + ex.getMessage(), e);
        }
        return null;
    }

And then of course in my json-servlet.xml file:

<bean id="exceptionResolver" class="com.autolytix.common.web.CustomExceptionResolver"/>
ehrhardt
  • 2,346
  • 1
  • 19
  • 13
0

Add the following code to the frontcontrol

 @ExceptionHandler(Exception.class)
public @ResponseBody
MyErrorBean handleGeneralException(Exception e,
        HttpServletRequest request, HttpServletResponse response) {
    logger.info("Exception:" , e);
    response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    MyErrorBean errorBean = new MyErrorBean();
    errorBean.setStatus(MyConstants.ERROR_STATUS);
    return errorBean;

}`

Since you are using JSON I assume you will have a messageconverter configured in your code which will convert this to JSON. By setting the status and sending a bean you will be able to solve it.

Raghav
  • 1,014
  • 2
  • 16
  • 34