13

with respect to javax.validation

  @NotNull(message = "From can't be null")
  @Min(value = 1, message = "From must be greater than zero")
  private Long from;
  @NotNull(message = "To can't be null")
  @Min(value = 1, message = "To must be greater than zero")
  private Long to;

I want to also validate that FROM should be less than TO and TO should be greater than FROM ? how we can do this using javax validation's annotation ?

Shahid Ghafoor
  • 2,991
  • 17
  • 68
  • 123

2 Answers2

5

You need a custom cross field validation annotation.

One way is to annotate your custom class with @YourCustomAnnotation.

In YourCustomAnnotationValidator you have access to your value, hence you can implement your logic there:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Constraint(validatedBy = DateValidator.class)
public @interface RangeCheck {

    String message();

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

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

public class RangeCheckValidtor implements ConstraintValidator<RangeCheck, YourDto> {

    @Override
    public void initialize(RangeCheck date) {
        // Nothing here
    }

    @Override
    public boolean isValid(YourDto dto, ConstraintValidatorContext constraintValidatorContext) {
        if (dto.getFrom() == null || dto.getTo() == null) {
            return true;
        }
        return from < to;
    }
}

Then mark your YourDto class with @RangeCheck:

@RangeCheck(message = "your messgae")
public class YourDto {
   // from
   // to
}

Or simply manually validate the relation of two fields.

Mạnh Quyết Nguyễn
  • 17,677
  • 1
  • 23
  • 51
0

For anyone who needs a more generic solution, validator can be implemented to compare two given property. Here is what I came up with :

@Target({ TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = { CompareFieldsValidator.class })
public @interface CompareFields {

    String message() default "{CompareFields.message}";

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

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

    String left();

    String right();
    
    int[] vals() default {0};

    @Target({ TYPE, ANNOTATION_TYPE })
    @Retention(RUNTIME)
    public @interface List {
        CompareFields[] value();
    }
}


public class CompareFieldsValidator implements ConstraintValidator<CompareFields, Object> {

    private static final Logger log = LoggerFactory.getLogger(FieldsEqualityValidator.class);

    private String left;
    private String right;
    private Set<Integer> vals;

    @Override
    public void initialize(CompareFields constraintAnnotation) {
        left = constraintAnnotation.left();
        right = constraintAnnotation.right();
        vals = Arrays.stream(constraintAnnotation.vals()).boxed().collect(Collectors.toSet());
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        if (value == null)
            return true;

        try {
            Class<?> clazz = value.getClass();
            //get left operand as comparable
            Field leftField = ReflectionUtils.findField(clazz, left);
            leftField.setAccessible(true);
            Object leftObj = leftField.get(value);
            Comparable<Object> leftComparable = (Comparable<Object>)leftObj;
            //get right operand
            Field rightField = ReflectionUtils.findField(clazz, right);
            rightField.setAccessible(true);
            Object rightObj = rightField.get(value);
            
            //null check : return true if both null
            if(leftComparable == null) {
                if(rightObj == null) {
                    return true;
                } else {
                    context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
                        .addPropertyNode(left).addPropertyNode(right);
                    return false;
                }
            }
            
            //return true if compareTo result in vals array
            if(vals.contains(leftComparable.compareTo(rightObj))) {
                return true;
            }else {
                context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
                .addPropertyNode(left).addConstraintViolation();
                return false;
            }
        } catch (Exception e) {
            log.error("Cannot validate one of two field in '" + value + "'!", e);
            return false;
        }
    }
}


//Usage: selling price must be smallar than or equal to list price
@CompareFields(left = "sellingPrice", right = "listPrice", vals = {-1,0})
public class InventoryDto{
...
}
//Usage: start time must be before finish time
@CompareFields(left = "startTime", right = "finishTime", vals = {-1})
public class GateLogDto{
...
}
  • 1
    As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jul 09 '23 at 23:26