15
votes

I am working on a REST api. Receiving a POST message with bad JSON (e.g. { sdfasdfasdf } ) causes Spring to return the default server page for a 400 Bad Request Error. I do not want to return a page, I want to return a custom JSON Error object.

I can do this when there is an exception thrown by using an @ExceptionHandler. So if it is a blank request or a blank JSON object (e.g. { } ), it will throw a NullPointerException and I can catch it with my ExceptionHandler and do whatever I please.

The problem then, is that Spring doesn't actually throw an exception when it is just invalid syntax... at least not that I can see. It simply returns the default error page from the server, whether it is Tomcat, Glassfish, etc.

So my question is how can I "intercept" Spring and cause it to use my exception handler or otherwise prevent the error page from displaying and instead return a JSON error object?

Here is my code:

@RequestMapping(value = "/trackingNumbers", method = RequestMethod.POST, consumes = "application/json")
@ResponseBody
public ResponseEntity<String> setTrackingNumber(@RequestBody TrackingNumber trackingNumber) {

    HttpStatus status = null;
    ResponseStatus responseStatus = null;
    String result = null;
    ObjectMapper mapper = new ObjectMapper();

    trackingNumbersService.setTrackingNumber(trackingNumber);
    status = HttpStatus.CREATED;
    result = trackingNumber.getCompany();


    ResponseEntity<String> response = new ResponseEntity<String>(result, status);

    return response;    
}

@ExceptionHandler({NullPointerException.class, EOFException.class})
@ResponseBody
public ResponseEntity<String> resolveException()
{
    HttpStatus status = null;
    ResponseStatus responseStatus = null;
    String result = null;
    ObjectMapper mapper = new ObjectMapper();

    responseStatus = new ResponseStatus("400", "That is not a valid form for a TrackingNumber object " + 
            "({\"company\":\"EXAMPLE\",\"pro_bill_id\":\"EXAMPLE123\",\"tracking_num\":\"EXAMPLE123\"})");
    status = HttpStatus.BAD_REQUEST;

    try {
        result = mapper.writeValueAsString(responseStatus);
    } catch (IOException e1) {
        e1.printStackTrace();
    }

    ResponseEntity<String> response = new ResponseEntity<String>(result, status);

    return response;
}
1
The thread here explains what is most likely going on: stackoverflow.com/questions/3230358/… Basically, the ExceptionHandler method applies only to RequestMappings in its own class. For your case, the request is not reaching any mappings in that class since the json is invalid. Use either a HandlerExceptionResolver or ControllerAdvice if you are on Spring 3.2 - Matt
Check different question with answer which might help you - stackoverflow.com/questions/17183102/… - Pavel Horal
@PavelHoral this is what my answer describes - using HttpMessageNotReadableException but with all the history behind how it was added and which version of Spring it can be used in! - andyb
@andyb Your answer is more like a personal story with partially correct answer in the end ;). This question is simply about how to handle invalid JSON response IMO... the problem of missing Content-Type and Spring being unable to find correct handler is not much related. - Pavel Horal
@PavelHoral The question is concerned with what exception to catch for invalid POST body. There is nothing wrong with explaining how an empty POST body is handled internally by Spring and also showing how to catch the correct exception and explaining which version of Spring this was added. - andyb

1 Answers

20
votes

This was raised as an issue with Spring SPR-7439 - JSON (jackson) @RequestBody marshalling throws awkward exception - which was fixed in Spring 3.1M2 by having Spring throw a org.springframework.http.converter.HttpMessageNotReadableException in the case of a missing or invalid message body.

In your code you cannot create a ResponseStatus since it is abstract but I tested catching this exception with a simpler method locally with Spring 3.2.0.RELEASE running on Jetty 9.0.3.v20130506.

@ExceptionHandler({org.springframework.http.converter.HttpMessageNotReadableException.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public String resolveException() {
    return "error";
}

and I received a 400 status "error" String response.

The defect was discussed on this Spring forum post.

Note: I started testing with Jetty 9.0.0.M4 but that had some other internal issues stopping the @ExceptionHandler completing, so depending on your container (Jetty, Tomcat, other) version you might need to get a newer version that plays nicely with whatever version of Spring you are using.