Home > database >  ConstraintValidator throws both default and custom error in BindingResult
ConstraintValidator throws both default and custom error in BindingResult

Time:11-16

I have some code here with a ConstraintValidator that validates an object by comparing two of its attributes.

Here is the object, the annotation that is of interest to us is @ValidSmsTextLength:

@ValidSmsTextLength(groups = { PostGroup.class, PatchGroup.class, PostMessageCampaignGroup.class })
@JsonDeserialize(as = SmsMessageDto.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({"text", "encoding", "messagePartCount", "length"})
public class SmsMessageDto extends AbstractRestDto implements OneOfMessage {

  @NotEmpty(message = "SMS_TEXT_NULL_OR_EMPTY", groups = { PostGroup.class, PatchGroup.class })
  @JsonProperty("text")
  private String text = null;

  @ValidParameterByEnum(enumValid = EncodingEnum.class, message = "INVALID_ENCODING_ENUM", groups = {PostGroup.class, PostMessageCampaignGroup.class})
  @JsonProperty("encoding")
  private EncodingEnum encoding = EncodingEnum.GSM7;

This object SmsMessageDto, which implements OneOfMessage, is an attribute (named "body") of that object:

public class AbstractMessageDto extends AbstractRestResourceDto {

    @JsonDeserialize(using = OneOfMessageDtoDeserializer.class)
    @Valid
    @NotNull(message = "MESSAGE_NULL", groups = PostGroup.class)
    @JsonProperty("body")
    protected OneOfMessage body = null;

And here are the Interface and ConstraintValidator associated with the annotation @ValidSmsTextLength:

@Constraint(validatedBy = SmsTextLengthValidator.class)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidSmsTextLength {

    String message() default "DEFAULT_SMS_TEXT_LENGTH_MESSAGE";

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

    Class<? extends Payload>[] payload() default {};
}
public class SmsTextLengthValidator implements ConstraintValidator<ValidSmsTextLength, SmsMessageDto> {

    private static final String TEXT = "text";

    @Override
    public boolean isValid(SmsMessageDto smsMessageDto, ConstraintValidatorContext constraintValidatorContext) {

        EncodingEnum encodingEnum = smsMessageDto.getEncoding();
        if (smsMessageDto.getText() != null && EncodingEnum.GSM7.equals(encodingEnum) && smsMessageDto.getText().length() > 1530) {
            constraintValidatorContext
                    .buildConstraintViolationWithTemplate("SMS_TEXT_LENGTH_GSM7_ERROR")
                    .addPropertyNode(TEXT)
                    .addConstraintViolation();
            return false;
        } else if (smsMessageDto.getText() != null && EncodingEnum.UNICODE.equals(encodingEnum) && smsMessageDto.getText().length() > 670) {
            constraintValidatorContext
                    .buildConstraintViolationWithTemplate("SMS_TEXT_LENGTH_UNICODE_ERROR")
                    .addPropertyNode(TEXT)
                    .addConstraintViolation();
            return false;
        }
        return true;
    }
}

I implemented a test where I validate (through a @Validated annotated controller method) a AbstractMessageDto object which has a text attribute that satisfy the first if() condition in the isValid() method.

The BindingResult object present in my controller method ends up containing two errors:

  • one that has been created with the default message ("DEFAULT_SMS_TEXT_LENGTH_MESSAGE"), where the field is "body".
  • and one created with the correct isValid() message ("SMS_TEXT_LENGTH_GSM7_ERROR" for my test), where the field is "body.text" (because of the addPropertyNode(TEXT) in isValid()).

I would like for my BindingResult to not contain the error initialized with the default message. How can I stop the ConstraintValidator from creating that error and adding it to the BindingResult object?

CodePudding user response:

You need to call context.disableDefaultConstraintViolation().

One more suggestion for your validator: if the smsMessageDto is null, you will get a NullPointerException, because the @NotNull hasn't caused an error yet (the validation is still taking place). Add the following as first lines of the method:

if (smsMessageDto == null) {
    // handled by @NotNull
    return true;
}
  • Related