I have an @ExceptionHandler(MethodArgumentNotValidException.class) that returns HTTP code 400 when a validation fails. I created a customized annotation and I need to change the HTTP code to 422. But, as the exception handler is already annotated with @ResponseStatus(code = HttpStatus.BAD_REQUEST), How could I do it?
I thought that maybe would be possible to make my customized annotation to throw a customized exception and then capture it. Is that possible? How?
My RestControllerAdvice:
@RestControllerAdvice
public class ManipuladorDeExcecoes {
@Autowired
MessageSource messageSource;
@ResponseStatus(code = HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public List<ErroPadrao> handle(MethodArgumentNotValidException exception) {
List<ErroPadrao> listaDeErros = new ArrayList<>();
List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();
fieldErrors.forEach(e -> {
String mensagem = messageSource.getMessage(e, LocaleContextHolder.getLocale());
ErroPadrao erro = new ErroPadrao(e.getField(), mensagem);
listaDeErros.add(erro);
});
return listaDeErros;
}
}
My custom annotation:
@Constraint(validatedBy = ValorUnicoValidator.class )
@Target({ FIELD })
@Retention(RUNTIME)
public @interface ValorUnico {
String message() default "O valor informado já existe no banco de dados";
String campo();
Class<?> tabela();
boolean removeStrings() default false;
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
My validator:
public class ValorUnicoValidator implements ConstraintValidator<ValorUnico, Object> {
@PersistenceContext
EntityManager entityManager;
private String campo;
private Class<?> tabela;
private boolean removeStrings;
@Override
public void initialize(ValorUnico constraintAnnotation) {
this.campo = constraintAnnotation.campo();
this.tabela = constraintAnnotation.tabela();
this.removeStrings = constraintAnnotation.removeStrings();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
if (removeStrings) {
value = value.toString().replaceAll("[^0-9]", "");
}
Boolean valorJaExiste = entityManager
.createQuery("SELECT COUNT(t) < 1 FROM " + tabela.getName() + " t WHERE "
+ campo + " = :pValor", Boolean.class)
.setParameter("pValor", value)
.getSingleResult();
return valorJaExiste;
}
}
I am not aware of any way to make the validator throw custom exceptions. Here, someone works around this by throwing custom exceptions manually (in every request mapping that has a parameter of the relevant type); you can do what they did and then write an @ExceptionHandler
for your custom exception class. I think there's a better way, though.
You can check the message for the constraint that failed in your @ExceptionHandler
, and return different status codes based on that. (You'll need to remove the @ResponseStatus
annotation and change the return type of the method, but there are many permitted return types for @ExceptionHandler
methods -- including ResponseEntity
, whose status you can set to anything you like.) So, something like
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Void> handleInvalid(MethodArgumentNotValidException e) {
for (FieldError err: e.getFieldErrors()) {
if (err.getDefaultMessage().equals("O valor informado já existe no banco de dados")) {
return ResponseEntity.unprocessableEntity().build();
}
}
return ResponseEntity.badRequest().build();
}
Obviously, you'll want to turn that message string into a constant, since you don't want to have to remember to change it both in the @ExceptionHandler
method and the annotation itself.