6

long story short: I'm creating API that is supposed to be 100% REST. I'm trying to overwrite default response for the following case: I've got a method in my @RestController that has @RequestBody as an attribute

@RequestMapping(value = {"register"}, method = RequestMethod.POST, produces = "application/hal+json")
public Resource<User> registerClient(@RequestBody User user, HttpServletRequest request)

and the method is working just fine if I send a proper request. But there is a problem when I don't. When a request has empty body, I get a generic Tomcat error page for status 400 and I need it to send just a string or a JSON object instead.

So far I tried to add Exception Handlers in my RestControllerAdvice for all Spring exceptions from package org.springframework.web.binding, but it didn't work either.

I'm already aware that for some security-related errors one have to create handlers in configuration, but I don't know if this is the case.

Did anyone face similar issues? Is there something I'm missing?

mr_m1m3
  • 323
  • 1
  • 2
  • 12
  • "But there is a problem when I don't." In which cases you don't returning User resource? – ruslanys Sep 01 '17 at 10:09
  • @ruslanys I mean the case when I send a POST request with no body or with empty body, whenever I POST a body with at least **{}** it's handled as designed – mr_m1m3 Sep 01 '17 at 10:11
  • @ruslanys unfortunately it doesn't help, the Content-Type header is there, but still - no body in request brings mentioned issue – mr_m1m3 Sep 01 '17 at 10:15
  • Try to send request with Content-Type. I suppose the problem is with it. Just check the request: Content-Type: application/json It should works. Cos Spring making a validation only if `@Validated` annotation is present. Otherwise, you are missing `Content-Type`, I suppose. – ruslanys Sep 01 '17 at 10:16
  • Oh, is this case, something wrong not around the controller – ruslanys Sep 01 '17 at 10:17
  • Probably yes, but where to look for the issue in that case? – mr_m1m3 Sep 01 '17 at 10:43
  • Do you have any Spring Security context? If yes, just walk through filterChain stack trace and you'll find the place when this Exception is creating. – ruslanys Sep 01 '17 at 10:47

3 Answers3

8

The solution was to simply put required = false in RequestBody annotation. After that, I could easily add some logic to throw custom exception and handle it in ControllerAdvice.

@RequestMapping(value = {"register"}, method = RequestMethod.POST, produces = "application/hal+json")
public Resource<User> registerClient(@RequestBody(required = false) User user, HttpServletRequest request){
    logger.debug("addClient() requested from {}; registration of user ({})", getClientIp(request), user);
    if(user == null){
        throw new BadRequestException()
                .setErrorCode(ErrorCode.USER_IS_NULL.toString())
                .setErrorMessage("Wrong body or no body in reqest");
    } (...)
mr_m1m3
  • 323
  • 1
  • 2
  • 12
4

Firstly I suggest you to use BindingResult as a parameter of the POST call and check if it returns an error or not.

@RequestMapping(value = {"register"}, method = RequestMethod.POST, produces = "application/hal+json")
public ResponseEntity<?> registerClient(@RequestBody User user, HttpServletRequest request, BindingResult brs)
    if (!brs.hasErrors()) {
        // add the new one
        return new ResponseEntity<User>(user, HttpStatus.CREATED);
    }
    return new ResponseEntity<String>(brs.toString(), HttpStatus.BAD_REQUEST);
}

Secondly, the call can throw some of errors, a good practice is to carch them and return them itself or transform them to your own exception object. The advantage is it secures a call of all the update/modify methods (POST, PUT, PATCH)

@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public ResponseEntity<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
    return new ResponseEntity<List<MethodArgumentNotValidException>>(e, HttpStatus.BAD_REQUEST);
}

@ExceptionHandler({HttpMessageNotReadableException.class})
@ResponseBody
public ResponseEntity<?> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
    return new ResponseEntity<List<HttpMessageNotReadableException>>(e, HttpStatus.BAD_REQUEST);
}
Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183
0

Your control will never reach to your request method under normal circumstances. If you want a looking good page you can make use of web.xml and configure it to produce your answer.

<error-page>
    <error-code>404</error-code>
    <location>/pages/resource-not-found.html</location>
</error-page>

Generally, if you want to go past this 400 problem, you will have to add a few annotiations to your User.java to avoid any unknown fields while de-serializing.

Gaurav
  • 3,614
  • 3
  • 30
  • 51