-1

I have a custom validator and need to include message parameters to construct the error message dynamically.

public class DescriptionLengthValidator
    implements ConstraintValidator<ValidTypeDescriptionLength, RequestDto> {

  @Override
  public boolean isValid(RequestDto request, ConstraintValidatorContext context) {
    int maxSize = request.getType().equals("Category1") ? 50 : 250;

    boolean isValid = request.getDescription().length() <= maxSize;
    if (!isValid) {

      HibernateConstraintValidatorContext hibernateContext =
          context.unwrap(HibernateConstraintValidatorContext.class);

      hibernateContext.disableDefaultConstraintViolation();
      hibernateContext.addMessageParameter("0", "0").addMessageParameter("1", maxSize)
          .buildConstraintViolationWithTemplate("error.Size").addPropertyNode("description")

          .addConstraintViolation();

      return false;
    }

    return true;
  }
}

Annotation is defined as follows.

@Constraint(validatedBy = DescriptionLengthValidator.class)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidTypeDescriptionLength {
  String message() default "error.Size";

  Class<?>[] groups() default {};

  Class<? extends Payload>[] payload() default {};
}

The Controller advice is as follows.

@ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
  @Override
  protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
      HttpHeaders headers, HttpStatus status, WebRequest request) {

    Map<String, String> fieldErrors = ex.getBindingResult().getFieldErrors().stream().collect(
        Collectors.toMap(FieldError::getField, fe -> getFieldErrorMessage(fe,
            String.format("Invalid value [%s]", fe.getRejectedValue()))));


    return handleFieldValidationErrors(ex, status, request, fieldErrors);
  }

  private String getFieldErrorMessage(FieldError error, String defaultMessage) {
    Object[] args = error.getArguments();
    if (args != null && args.length > 0 && args[0] instanceof MessageSourceResolvable) {
      args = Arrays.copyOfRange(args, 1, args.length);
    }
    List<String> errorCodes = new ArrayList<>();
    if (error.getCodes() != null) {
      Arrays.stream(error.getCodes()).map(s -> "error." + s).forEach(errorCodes::add);
    }
    if (error.getDefaultMessage() != null) {
      errorCodes.add(error.getDefaultMessage());
    }
    return service.getMessage(args, defaultMessage);
  }
}

When accessing arguments by calling error.getArguments(), message parameters are not available. How can I modify custom validator/Controller advice to access massage parameters?

2 Answers 2

1

The arguments of the error error.getArguments() are not what you think. They give your which arguments of the MethodArgumentNotValidException are not valid. Not the arguments of the message.

error.getDefaultMessage() should already return the full message with parameter injected. This is how it is supposed to work, and this is how hibernate is implementing it for its own validators. See for example here (messages.properties) and there (the annotation). You can also play with standard @Min and @Max and you will see that the value is injected in the message automatically in error.getDefaultMessage()

I think your mistake is that the default message is missing curling braces

@Constraint(validatedBy = DescriptionLengthValidator.class)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidTypeDescriptionLength {
  String message() default "{error.Size}"; // here

  Class<?>[] groups() default {};

  Class<? extends Payload>[] payload() default {};
}

Then please also check that in your messages.properties. The error message should be there with the parametrized placeholder name you used by addMessageParameter(name, value)

error.Size=Error size. Should be between {0} and {1}

At this point error.getDefaultMessage() should return Error size. Should be between 0 and 250 or Error size. Should be between 0 and 50

Sign up to request clarification or add additional context in comments.

Comments

0

I was able to access the message parameters by modifying getFieldErrorMessage method as below.

  private String getFieldErrorMessage(FieldError error, String defaultMessage) {
        Object[] args;
    
        ConstraintViolation<?> violation = error.unwrap(ConstraintViolation.class);
        Map<String, Object> messageParameters =
            ((ConstraintViolationImpl<?>) violation).getMessageParameters();
    
        if (!messageParameters.isEmpty()) {
          args = messageParameters.values().toArray();
        } else {
          args = error.getArguments();
        }
     // rest of the code

    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.