Spring Validation: Requiring an exact length on an optional field

Marvo picture Marvo · Feb 10, 2015 · Viewed 19.1k times · Source

In Spring MVC, I'm attempting to use annotations to perform validation of form input. I have some fields that are optional, but when input is provided, it must meet certain criteria. For example, I have one field that can be left blank, but when it is filled in, the answer must be exactly five characters long.

I've tried things like

@Size(min=5, max=5)
private String value;

but that gives the user an error if the user doesn't fill in the field.

How can I provide some "rules" for an optional field?

Answer

mavarazy picture mavarazy · Feb 10, 2015
  1. First option would be to set value to null.

If you take a look at SizeValidatorForCharSequence, used by hibernate:

/**
 * Check that the length of a character sequence is between min and max.
 *
 * @author Emmanuel Bernard
 * @author Gavin King
 * @author Hardy Ferentschik
 */
public class SizeValidatorForCharSequence implements ConstraintValidator<Size, CharSequence> {

    private static final Log log = LoggerFactory.make();

    private int min;
    private int max;

    public void initialize(Size parameters) {
        min = parameters.min();
        max = parameters.max();
        validateParameters();
    }

    /**
     * Checks the length of the specified character sequence (e.g. string).
     *
     * @param charSequence The character sequence to validate.
     * @param constraintValidatorContext context in which the constraint is evaluated.
     *
     * @return Returns <code>true</code> if the string is <code>null</code> or the length of <code>charSequence</code> between the specified
     *         <code>min</code> and <code>max</code> values (inclusive), <code>false</code> otherwise.
     */
    public boolean isValid(CharSequence charSequence, ConstraintValidatorContext constraintValidatorContext) {
        if ( charSequence == null ) {
            return true;
        }
        int length = charSequence.length();
        return length >= min && length <= max;
    }

    private void validateParameters() {
        if ( min < 0 ) {
            throw log.getMinCannotBeNegativeException();
        }
        if ( max < 0 ) {
            throw log.getMaxCannotBeNegativeException();
        }
        if ( max < min ) {
            throw log.getLengthCannotBeNegativeException();
        }
    }
}

Size is invalid only if it's length does not match min, max constraints, and it's fine, if value is null.

  1. Another option would be to create your own annotation and validator:

    @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
    @Retention(RUNTIME)
    @Documented
    @Constraint(validatedBy = { StringEmptyOrSizeValidator.class })
    public @interface StringEmptyOrSize {
    
        String message() default "{javax.validation.constraints.Size.message}";
    
        Class<?>[] groups() default { };
    
        Class<? extends Payload>[] payload() default { };
    
        /**
         * @return size the element must be higher or equal to
         */
        int min() default 0;
    
        /**
         * @return size the element must be lower or equal to
         */
        int max() default Integer.MAX_VALUE;
    
        /**
         * Defines several {@link Size} annotations on the same element.
         *
         * @see Size
         */
        @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
        @Retention(RUNTIME)
        @Documented
        @interface List {
    
            Size[] value();
        }
    }
    

with validator

public class StringEmptyOrSizeValidator implements ConstraintValidator<Size, CharSequence> {

    ....
    public boolean isValid(CharSequence charSequence, ConstraintValidatorContext constraintValidatorContext) {
        if ( charSequence == null || charSequence.isEmpty()) {
            return true;
        }
        int length = charSequence.length();
        return length >= min && length <= max;
    }

    ....
}