javahibernate-validatorjavax-validation

Java Bean Validation using @DecimalMin @DecimalMax but also allow empty string?


I came across a strange case that given following model class:

@DecimalMin("-10")
@DecimalMax("10")
String position;

And the client has a request that sets position to be an empty string "" and expects the validation to pass. This is something I cannot negotiate, so is there any way to work around such case? Something like:

@DecimalMin("-10")
@DecimalMax("10")
@Empty
String position;

Solution

  • There are at least two ways you could approach this.

    A simpler solution would be to create your own getter method for this property and move the annotations to the getter level instead of keeping them on the field. This way, you'd be able to add some logic to what is returned by the getter:

    public class PojoWithGetter {
        private String position;
        //....
        @DecimalMin("-10")
        @DecimalMax("10")
        public String getPosition() {
            return "".equals( this.position ) ? null : this.position;
        }
    }
    

    Doing so would allow you to leverage the use of default validators bundled with Hibernate Validator by making relatively small changes.


    Alternatively, you could provide your own implementation of DecimalMin/DecimalMax validators. This can be done either through XML (see an example "Using XML to register a regular expression based constraint definition for @URL" at the end of the section) - or programmatically. Here's an example of how you'd do it with the code:

    HibernateValidatorConfiguration configuration = Validation
            .byProvider( HibernateValidator.class )
            .configure();
    
    ConstraintMapping constraintMapping = configuration.createConstraintMapping();
    
    constraintMapping
            .constraintDefinition( DecimalMax.class )
            // NOTE: you are excluding default validators. So you'd need to add any 
            // others for different types that you need explicitly. 
            .includeExistingValidators( false )
            .validatedBy( CustomDecimalMaxCharSequenceValidator.class );
    // same for DecimalMin and any others ...
    
    configuration.addMapping( constraintMapping );
    
    Validator validator = configuration.buildValidatorFactory().getValidator();
    // ... 
    

    And your custom validator can look something like:

    public class CustomDecimalMaxCharSequenceValidator extends DecimalMaxValidatorForCharSequence {
        @Override
        public boolean isValid(CharSequence charSequence, ConstraintValidatorContext constraintValidatorContext) {
            if ( "".equals( charSequence ) ) {
                return true;
            }
            return super.isValid( charSequence, constraintValidatorContext );
        }
    }
    

    But this is much more cumbersome and removes the default validators that you would have to add back using the same approach.