javaspringspring-validatorjavax.validation

A custom annotation can throw a custom exception, rather than MethodArgumentNotValidException?


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;
  }
}

Solution

  • 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.