If I use a constraint like this @NotNull and then in the controller
public User createUser(
@Validated
@RequestBody User user) {}
It gives a really nice 400 exception with details.
But if I use my own custom validator like this:
public User createUser(
@UserConstraint
@RequestBody User user) {}
It throws a 500 server error like this:
javax.validation.ConstraintViolationException: createUser.user: Error with field: 'test35'
at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:117) ~[spring-context-5.1.10.RELEASE.jar:5.1.10.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.10.RELEASE.jar:5.1.10.RELEASE]
at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:69) ~[spring-security-core-5.1.6.RELEASE.jar:5.1.6.RELEASE]
Is there a way to get nice 400 message to the response?
Ideally the 400 message should be the same as Spring's validation JSON
{
"timestamp": "2019-10-30T02:33:15.489+0000",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"Size.user.lastName",
"Size.lastName",
"Size.java.lang.String",
"Size"
],
"arguments": [
{
"codes": [
"user.lastName",
"lastName"
],
"arguments": null,
"defaultMessage": "lastName",
"code": "lastName"
},
25,
1
],
"defaultMessage": "size must be between 1 and 25",
"objectName": "user",
"field": "lastName",
"rejectedValue": "",
"bindingFailure": false,
"code": "Size"
}
],
"message": "Validation failed for object='user'. Error count: 1",
"path": "/api/v1/users"
}
Yes, you can create a custom error handler
then you can add anything on your response and status as well. This is the simple way to change the status:
1.- Simple way to change status
when ConstraintViolationException
is thrown.
import javax.validation.ConstraintViolationException;
@ControllerAdvice
public class CustomErrorHandler {
@ExceptionHandler(ConstraintViolationException.class)
public void handleConstraintViolationException(ConstraintViolationException exception,
ServletWebRequest webRequest) throws IOException {
webRequest.getResponse().sendError(HttpStatus.BAD_REQUEST.value(), exception.getMessage());
}
}
2.- Custom way to put the response when a ConstraintViolationException
occurs.
@ControllerAdvice
public class CustomErrorHandler {
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<CustomError> handleConstraintViolationException(ConstraintViolationException exception) {
CustomError customError = new CustomError();
customError.setStatus(HttpStatus.BAD_REQUEST);
customError.setMessage(exception.getMessage());
customError.addConstraintErrors(exception.getConstraintViolations());
return ResponseEntity.badRequest().body(customError);
}
}
As the solution above doesn't really produce the desired result here a link which might help: https://sterl.org/2020/02/spring-boot-hateoas-jsr303-validation/
Funny enough spring behaves differently if the class or the method request body is annotated with @Validated.
In other words on the class, you might encounter 500 errors. If you move the validation annotation, as you already did, into the method, the normal behavior should be 400.
Long story short, as soon as you have your custom contains, etc. you need to adjust the stuff a bit -- as in Spring, it is the MethodArgumentNotValidException and not the ConstraintViolationException, for which Spring already as a controller advice.
A quick solution may look like:
@Autowired
private MessageSource messageSource;
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(ConstraintViolationException.class)
public @ResponseBody Map<String, Object> handleConstraintViolation(ConstraintViolationException e, ServletWebRequest request) {
// emulate Spring DefaultErrorAttributes
final Map<String, Object> result = new LinkedHashMap<>();
result.put("timestamp", new Date());
result.put("path", request.getRequest().getRequestURI());
result.put("status", HttpStatus.BAD_REQUEST.value());
result.put("error", HttpStatus.BAD_REQUEST.getReasonPhrase());
result.put("message", e.getMessage());
result.put("errors", e.getConstraintViolations().stream().map(cv -> SimpleObjectError.from(cv, messageSource, request.getLocale())));
return result;
}
@Getter @ToString
static class SimpleObjectError {
String defaultMessage;
String objectName;
String field;
Object rejectedValue;
String code;
public static SimpleObjectError from(ConstraintViolation<?> violation, MessageSource msgSrc, Locale locale) {
SimpleObjectError result = new SimpleObjectError();
result.defaultMessage = msgSrc.getMessage(violation.getMessageTemplate(),
new Object[] { violation.getLeafBean().getClass().getSimpleName(), violation.getPropertyPath().toString(),
violation.getInvalidValue() }, violation.getMessage(), locale);
result.objectName = Introspector.decapitalize(violation.getRootBean().getClass().getSimpleName());
result.field = String.valueOf(violation.getPropertyPath());
result.rejectedValue = violation.getInvalidValue();
result.code = violation.getMessageTemplate();
return result;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With