简体   繁体   中英

Why am I getting code 400 response instead of error json object in tomcat 9 with spring 5?

The controller:

@RestController
@RequestMapping(value = "/test", produces = MediaType.APPLICATION_JSON_VALUE)
@Validated
public class ApiController {


    @PostMapping(value = "/in",
            consumes = MediaType.APPLICATION_JSON_VALUE)
    ResponseEntity<InitResponse> inPost(
            @ApiParam(required = true) @Valid @RequestBody InRequest inRequest) {
        LOG.info("inPost request was received = {}", inRequest);
        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
    }

The exception handler:

@ControllerAdvice(assignableTypes = ApiController .class, annotations = RestController.class)
public class InExceptionHandler {
    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity<INErrors> handleConstraintViolation(ConstraintViolationException ex) {

        LOG.info("handleConstraintViolation was trigerred");
        INError INError = new INError(HttpStatus.BAD_REQUEST.toString(), ex.getLocalizedMessage());
        return new ResponseEntity<>(new INErrors(), HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<INErrors> handleMethodArgumentConstraintViolation(MethodArgumentNotValidException ex) {

        BindingResult result = ex.getBindingResult();
        List<FieldError> fieldErrors = result.getFieldErrors();

        return new ResponseEntity<>(processFieldErrors(fieldErrors), HttpStatus.BAD_REQUEST);
    }
}

If the InRequest has all fields within the javax validation constraint then I get the right code, but when a field doesn't match validation I just get 400 response code. There are other exception handlers defined but I've put breakpoints everywhere and nothing is triggered.

I also added the log4j property:

log4j.logger.org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod=DEBUG,stdout

but this didn't produce any additional output when debugging. I'm expecting to also to have the INErrors object sent back, but it doesn't even enter either one of the 2 handling methods.

This is happening because Spring's default exception handler handles all WebMvc's standard exceptions by itself and then delegates unhandled exceptions to user-defined @ExceptionHandler methods.

In your case @Valid constraint violation throws Spring's MethodArgumentNotValidException which is handled by ResponseEntityExceptionHandler#handleMethodArgumentNotValid . So, to change the default behaviour for this exception, you need to override this method in your @ControllerAdivce .

@ControllerAdvice(assignableTypes = ApiController .class, annotations = RestController.class)
public class InExceptionHandler extends ResponseEntityExceptionHandler {

    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        BindingResult result = ex.getBindingResult();
        List<FieldError> fieldErrors = result.getFieldErrors();

        return ResponseEntity.badRequest().body(processFieldErrors(fieldErrors));
    }
}

EDIT : I saw that you're using both assignableTypes and annotations for @ControllerAdvice exception handler. This makes Spring register one exception handler for all @RestController s. Try using either assignableTypes or annotations .

As an option, you can create your custom annotation for different exception handlers.

Following code prints "one" when invalid data supplied to /one and "two" when data was sent to "/two".

@RestController
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface One {}

@RestController
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Two {}

@One
class ControllerOne {

    @PostMapping("one")
    String a(@RequestBody @Valid Data data) {
        return data.value;
    }
}

@Two
class ControllerTwo {

    @PostMapping("two")
    String a(@RequestBody @Valid Data data) {
        return data.value;
    }
}

@ControllerAdvice(annotations = One.class)
class HandlerOne extends ResponseEntityExceptionHandler {
    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        return ResponseEntity.badRequest().body("one");
    }
}

@ControllerAdvice(annotations = Two.class)
class HandlerTwo extends ResponseEntityExceptionHandler {
    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        return ResponseEntity.badRequest().body("two");
    }
}

The javax annotation style validation for the model worked for me only when I added the method inside the controller:

public class ApiController {
...
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<INErrors> handleConstraintViolation(MethodArgumentNotValidException exception) {
        BindingResult result = exception.getBindingResult();
        List<FieldError> fieldErrors = result.getFieldErrors();
        return new ResponseEntity<>(processFieldErrors(fieldErrors), HttpStatus.BAD_REQUEST);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM