JSR 303 Validation, If one field equals "something", then these other fields should not be null JSR 303 Validation, If one field equals "something", then these other fields should not be null java java

JSR 303 Validation, If one field equals "something", then these other fields should not be null


Define method that must validate to true and put the @AssertTrue annotation on the top of it:

  @AssertTrue  private boolean isOk() {    return someField != something || otherField != null;  }

The method must start with 'is'.


In this case I suggest to write a custom validator, which will validate at class level (to allow us get access to object's fields) that one field is required only if another field has particular value. Note that you should write generic validator which gets 2 field names and work with only these 2 fields. To require more than one field you should add this validator for each field.

Use the following code as an idea (I've not test it).

  • Validator interface

    /** * Validates that field {@code dependFieldName} is not null if * field {@code fieldName} has value {@code fieldValue}. **/@Target({TYPE, ANNOTATION_TYPE})@Retention(RUNTIME)@Repeatable(NotNullIfAnotherFieldHasValue.List.class) // only with hibernate-validator >= 6.x@Constraint(validatedBy = NotNullIfAnotherFieldHasValueValidator.class)@Documentedpublic @interface NotNullIfAnotherFieldHasValue {    String fieldName();    String fieldValue();    String dependFieldName();    String message() default "{NotNullIfAnotherFieldHasValue.message}";    Class<?>[] groups() default {};    Class<? extends Payload>[] payload() default {};    @Target({TYPE, ANNOTATION_TYPE})    @Retention(RUNTIME)    @Documented    @interface List {        NotNullIfAnotherFieldHasValue[] value();    }}
  • Validator implementation

    /** * Implementation of {@link NotNullIfAnotherFieldHasValue} validator. **/public class NotNullIfAnotherFieldHasValueValidator    implements ConstraintValidator<NotNullIfAnotherFieldHasValue, Object> {    private String fieldName;    private String expectedFieldValue;    private String dependFieldName;    @Override    public void initialize(NotNullIfAnotherFieldHasValue annotation) {        fieldName          = annotation.fieldName();        expectedFieldValue = annotation.fieldValue();        dependFieldName    = annotation.dependFieldName();    }    @Override    public boolean isValid(Object value, ConstraintValidatorContext ctx) {        if (value == null) {            return true;        }        try {            String fieldValue       = BeanUtils.getProperty(value, fieldName);            String dependFieldValue = BeanUtils.getProperty(value, dependFieldName);            if (expectedFieldValue.equals(fieldValue) && dependFieldValue == null) {                ctx.disableDefaultConstraintViolation();                ctx.buildConstraintViolationWithTemplate(ctx.getDefaultConstraintMessageTemplate())                    .addNode(dependFieldName)                    .addConstraintViolation();                    return false;            }        } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ex) {            throw new RuntimeException(ex);        }        return true;    }}
  • Validator usage example (hibernate-validator >= 6 with Java 8+)

    @NotNullIfAnotherFieldHasValue(    fieldName = "status",    fieldValue = "Canceled",    dependFieldName = "fieldOne")@NotNullIfAnotherFieldHasValue(    fieldName = "status",    fieldValue = "Canceled",    dependFieldName = "fieldTwo")public class SampleBean {    private String status;    private String fieldOne;    private String fieldTwo;    // getters and setters omitted}
  • Validator usage example (hibernate-validator < 6; the old example)

    @NotNullIfAnotherFieldHasValue.List({    @NotNullIfAnotherFieldHasValue(        fieldName = "status",        fieldValue = "Canceled",        dependFieldName = "fieldOne"),    @NotNullIfAnotherFieldHasValue(        fieldName = "status",        fieldValue = "Canceled",        dependFieldName = "fieldTwo")})public class SampleBean {    private String status;    private String fieldOne;    private String fieldTwo;    // getters and setters omitted}

Note that validator implementation uses BeanUtils class from commons-beanutils library but you could also use BeanWrapperImpl from Spring Framework.

See also this great answer: Cross field validation with Hibernate Validator (JSR 303)


You should make use of custom DefaultGroupSequenceProvider<T>:

ConditionalValidation.java

// Marker interfacepublic interface ConditionalValidation {}

MyCustomFormSequenceProvider.java

public class MyCustomFormSequenceProvider    implements DefaultGroupSequenceProvider<MyCustomForm> {    @Override    public List<Class<?>> getValidationGroups(MyCustomForm myCustomForm) {        List<Class<?>> sequence = new ArrayList<>();        // Apply all validation rules from ConditionalValidation group        // only if someField has given value        if ("some value".equals(myCustomForm.getSomeField())) {            sequence.add(ConditionalValidation.class);        }        // Apply all validation rules from default group        sequence.add(MyCustomForm.class);        return sequence;    }}

MyCustomForm.java

@GroupSequenceProvider(MyCustomFormSequenceProvider.class)public class MyCustomForm {    private String someField;    @NotEmpty(groups = ConditionalValidation.class)    private String fieldTwo;    @NotEmpty(groups = ConditionalValidation.class)    private String fieldThree;    @NotEmpty    private String fieldAlwaysValidated;    // getters, setters omitted}

See also related question on this topic.