How to respond with HTTP 400 error in a Spring MVC @ResponseBody method returning String?

Solution 1:

change your return type to ResponseEntity<>, then you can use below for 400

return new ResponseEntity<>(HttpStatus.BAD_REQUEST);

and for correct request

return new ResponseEntity<>(json,HttpStatus.OK);

UPDATE 1

after spring 4.1 there are helper methods in ResponseEntity could be used as

return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);

and

return ResponseEntity.ok(json);

Solution 2:

Something like this should work, I'm not sure whether or not there is a simpler way:

@RequestMapping(value = "/matches/{matchId}", produces = "application/json")
@ResponseBody
public String match(@PathVariable String matchId, @RequestBody String body,
            HttpServletRequest request, HttpServletResponse response) {
    String json = matchService.getMatchJson(matchId);
    if (json == null) {
        response.setStatus( HttpServletResponse.SC_BAD_REQUEST  );
    }
    return json;
}

Solution 3:

Not necessarily the most compact way of doing this, but quite clean IMO

if(json == null) {
    throw new BadThingException();
}
...

@ExceptionHandler(BadThingException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public @ResponseBody MyError handleException(BadThingException e) {
    return new MyError("That doesnt work");
}

Edit you can use @ResponseBody in the exception handler method if using Spring 3.1+, otherwise use a ModelAndView or something.

https://jira.springsource.org/browse/SPR-6902

Solution 4:

I would change the implementation slightly:

First, I create a UnknownMatchException:

@ResponseStatus(HttpStatus.NOT_FOUND)
public class UnknownMatchException extends RuntimeException {
    public UnknownMatchException(String matchId) {
        super("Unknown match: " + matchId);
    }
}

Note the use of @ResponseStatus, which will be recognized by Spring's ResponseStatusExceptionResolver. If the exception is thrown, it will create a response with the corresponding response status. (I also took the liberty of changing the status code to 404 - Not Found which I find more appropriate for this use case, but you can stick to HttpStatus.BAD_REQUEST if you like.)


Next, I would change the MatchService to have the following signature:

interface MatchService {
    public Match findMatch(String matchId);
}

Finally, I would update the controller and delegate to Spring's MappingJackson2HttpMessageConverter to handle the JSON serialization automatically (it is added by default if you add Jackson to the classpath and add either @EnableWebMvc or <mvc:annotation-driven /> to your config, see the reference docs):

@RequestMapping(value = "/matches/{matchId}", produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Match match(@PathVariable String matchId) {
    // throws an UnknownMatchException if the matchId is not known 
    return matchService.findMatch(matchId);
}

Note, it is very common to separate the domain objects from the view objects or DTO objects. This can easily be achieved by adding a small DTO factory that returns the serializable JSON object:

@RequestMapping(value = "/matches/{matchId}", produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public MatchDTO match(@PathVariable String matchId) {
    Match match = matchService.findMatch(matchId);
    return MatchDtoFactory.createDTO(match);
}

Solution 5:

Here's a different approach. Create a custom Exception annotated with @ResponseStatus, like the following one.

@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "Not Found")
public class NotFoundException extends Exception {

    public NotFoundException() {
    }
}

And throw it when needed.

@RequestMapping(value = "/matches/{matchId}", produces = "application/json")
@ResponseBody
public String match(@PathVariable String matchId) {
    String json = matchService.getMatchJson(matchId);
    if (json == null) {
        throw new NotFoundException();
    }
    return json;
}

Check out the Spring documentation here: http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#mvc-ann-annotated-exceptions.