Following example shows how to create multiple validators (multiple implementations of ConstraintValidators ) for the same constraint.
Multiple validators for the same constraint might be needed if we want to target different elements specified by SupportedValidationTarget . If two ConstraintValidators refer to the same target type, an exception will occur.
Definition of SupportedValidationTarget(Version: java-bean-validation 6.2.0.Final) package javax.validation.constraintvalidation;
........
@Documented
@Target({TYPE})
@Retention(RUNTIME)
public @interface SupportedValidationTarget {
ValidationTarget[] value();
}
Definition of ValidationTarget(Version: java-bean-validation 6.2.0.Final) package javax.validation.constraintvalidation;
........
public enum ValidationTarget {
ANNOTATED_ELEMENT, 1
PARAMETERS 2
}
As seen in above definition, we can create two validators annotated with following annotations: - @SupportedValidationTarget(ValidationTarget.PARAMETERS) - @SupportedValidationTarget(ValidationTarget.ANNOTATED_ELEMENT)
In following example we are going to apply the same constraint two times (each tied to the two different validators implementations) on a single method. One constraint for validating return value (ValidationTarget.ANNOTATED_ELEMENT) of the method and the second constraint for validating parameter (ValidationTarget.PARAMETERS) of the method.
The attribute validationAppliesTo() requirement
When creating a constraint that is both generic (refers to ValidationTarget.ANNOTATED_ELEMENT) and cross-parameter (refers to ValidationTarget.PARAMETERS), the constraint annotation must include the validationAppliesTo() property:
ConstraintTarget validationAppliesTo() default ConstraintTarget.IMPLICIT;
This requirement is to remove the ambiguity when using same constraint for two target types.
As seen above, the type of the validationAppliesTo() parameter is ConstraintTarget . The default value must be ConstraintTarget.IMPLICIT.
Definition of ConstraintTarget(Version: java-bean-validation 6.2.0.Final) package javax.validation;
........
public enum ConstraintTarget {
IMPLICIT, 1
RETURN_VALUE, 2
PARAMETERS 3
}
Example
Our custom constraint definition
package com.logicbig.example;
import javax.validation.Constraint;
import javax.validation.ConstraintTarget;
import javax.validation.Payload;
import java.lang.annotation.*;
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {EvenNumbersInputValidator.class, EvenNumbersOutputValidator.class})
@Repeatable(EvenNumbers.List.class)
public @interface EvenNumbers {
String message() default "not valid even numbers: ${validatedValue}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
ConstraintTarget validationAppliesTo() default ConstraintTarget.IMPLICIT;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@interface List {
EvenNumbers[] value();
}
}
As seen above we used @Repeatable(EvenNumbers.List.class) , this is to use @EventNumbers annotation multiple times on the same method.
ConstraintValidator implementation for cross-parameters
package com.logicbig.example;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.constraintvalidation.SupportedValidationTarget;
import javax.validation.constraintvalidation.ValidationTarget;
import java.util.Arrays;
@SupportedValidationTarget(ValidationTarget.PARAMETERS)
public class EvenNumbersInputValidator implements
ConstraintValidator<EvenNumbers, Object[]> {
@Override
public void initialize(EvenNumbers constraintAnnotation) {
}
@Override
public boolean isValid(Object[] value, ConstraintValidatorContext context) {
if (value == null || value.length != 2 ||
!(value[0] instanceof Integer) ||
!(value[1] instanceof Integer)) {
return false;
}
Integer int1 = (Integer) value[0];
Integer int2 = (Integer) value[1];
if (int1 % 2 != 0 || int2 % 2 != 0) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("Either of the input parameters are not even " + Arrays
.toString(value))
.addConstraintViolation();
return false;
}
if (int1.compareTo(int2) > 0) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(String
.format("first input %s is not less than second input %s", int1, int2))
.addConstraintViolation();
return false;
}
return true;
}
}
Dynamically creating error messages via ConstraintValidatorContext#buildConstraintViolationWithTemplate()
In above code we used ConstraintValidatorContext#buildConstraintViolationWithTemplate() . This is to create violation messages within the validator. The violation message is interpolated.
Also to disable the default ConstraintViolation object generation (which is using the message template declared on the constraint) we used ConstraintValidatorContext#disableDefaultConstraintViolation() .
ConstraintValidator implementation for return value
package com.logicbig.example;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.constraintvalidation.SupportedValidationTarget;
import javax.validation.constraintvalidation.ValidationTarget;
import java.util.List;
@SupportedValidationTarget(ValidationTarget.ANNOTATED_ELEMENT)
public class EvenNumbersOutputValidator implements
ConstraintValidator<EvenNumbers, List<Integer>> {
@Override
public void initialize(EvenNumbers constraintAnnotation) { }
@Override
public boolean isValid(List<Integer> value, ConstraintValidatorContext context) {
if (value == null) {
return false;
}
for (Integer integer : value) {
if (integer % 2 != 0) {
return false;
}
}
return true;
}
}
Example bean
package com.logicbig.example;
import javax.validation.ConstraintTarget;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class MyBean {
@EvenNumbers(validationAppliesTo = ConstraintTarget.RETURN_VALUE)
@EvenNumbers(validationAppliesTo = ConstraintTarget.PARAMETERS)
public List<Integer> findEvenNumbersBetween(Integer startEven, Integer endEven) {
return IntStream.range(startEven, endEven + 1)
.filter(i -> i % 2 != 0)//purposely make it wrong
.boxed()
.collect(Collectors.toList());
}
}
Performing validation
package com.logicbig.example;
import javax.validation.*;
import javax.validation.executable.ExecutableValidator;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Set;
public class MultipleValidatorMain {
private static Validator validator;
public static void main(String[] args) throws NoSuchMethodException {
System.out.println("-- validating cross param --");
validateCrossParameters(7, 2);
validateCrossParameters(6, 2);
validateCrossParameters(2, 8);
System.out.println("-- validating return values --");
validateReturnValue();
}
private static void validateReturnValue() throws NoSuchMethodException {
MyBean myBean = new MyBean();
List<Integer> returnedValued = myBean.findEvenNumbersBetween(2, 8);
Method method = MyBean.class.getDeclaredMethod(
"findEvenNumbersBetween", Integer.class, Integer.class);
Validator validator = getValidator();
ExecutableValidator executableValidator = validator.forExecutables();
Set<ConstraintViolation<MyBean>> violations =
executableValidator.validateReturnValue(myBean, method, returnedValued);
if (violations.size() > 0) {
violations.stream().forEach(MultipleValidatorMain::printError);
} else {
System.out.println("returned value validation passed");
//proceed using object
}
}
private static void validateCrossParameters(int x, int y) throws NoSuchMethodException {
MyBean myBean = new MyBean();
Method method = MyBean.class.getDeclaredMethod("findEvenNumbersBetween",
Integer.class, Integer.class);
Validator validator = getValidator();
ExecutableValidator executableValidator = validator.forExecutables();
Set<ConstraintViolation<MyBean>> violations =
executableValidator.validateParameters(myBean, method, new Object[]{x, y});
if (violations.size() > 0) {
violations.stream().forEach(MultipleValidatorMain::printError);
} else {
System.out.println("cross parameter validation passed");
//proceed using object
}
}
private static Validator getValidator() {
if (validator == null) {
Configuration<?> config = Validation.byDefaultProvider().configure();
ValidatorFactory factory = config.buildValidatorFactory();
validator = factory.getValidator();
factory.close();
}
return validator;
}
private static void printError(
ConstraintViolation<MyBean> violation) {
System.out.println(violation.getPropertyPath() + " " + violation.getMessage());
}
}
Output-- validating cross param -- findEvenNumbersBetween.<cross-parameter> Either of the input parameters are not even [7, 2] findEvenNumbersBetween.<cross-parameter> first input 6 is not less than second input 2 cross parameter validation passed -- validating return values -- findEvenNumbersBetween.<return value> not valid even numbers: [3, 5, 7]
Example ProjectDependencies and Technologies Used: - hibernate-validator 6.2.0.Final (Hibernate's Jakarta Bean Validation reference implementation)
Version Compatibility: 5.0.0.Final - 6.2.0.Final Version compatibilities of hibernate-validator with this example: groupId: org.hibernate artifactId: hibernate-validator Reference implementation for Bean Validation 1.1
- 5.0.0.Final
- 5.0.1.Final
- 5.0.2.Final
- 5.0.3.Final
- 5.1.0.Final
- 5.1.1.Final
- 5.1.2.Final
- 5.1.3.Final
- 5.2.0.Final
- 5.2.1.Final
- 5.2.2.Final
- 5.2.3.Final
- 5.2.4.Final
- 5.2.5.Final
- 5.3.0.Final
- 5.3.1.Final
- 5.3.2.Final
- 5.3.3.Final
- 5.3.4.Final
- 5.3.5.Final
- 5.3.6.Final
- 5.4.0.Final
- 5.4.1.Final
- 5.4.2.Final
- 5.4.3.Final
groupId: org.hibernate.validator artifactId: hibernate-validator Reference implementation for Bean Validation 2.0
- 6.0.0.Final
- 6.0.1.Final
- 6.0.2.Final
- 6.0.3.Final
- 6.0.4.Final
- 6.0.5.Final
- 6.0.6.Final
- 6.0.7.Final
- 6.0.8.Final
- 6.0.9.Final
- 6.0.10.Final
- 6.0.11.Final
- 6.0.12.Final
- 6.0.13.Final
- 6.0.14.Final
- 6.0.15.Final
- 6.0.16.Final
- 6.0.17.Final
- 6.0.18.Final
- 6.0.19.Final
- 6.0.20.Final
- 6.0.21.Final
- 6.0.22.Final
- 6.1.0.Final
- 6.1.1.Final
- 6.1.2.Final
- 6.1.3.Final
- 6.1.4.Final
- 6.1.5.Final
- 6.1.6.Final
- 6.1.7.Final
- 6.2.0.Final
Version 7 and later: Jakarta Bean Validation 3.0 jakarta.* packages
Versions in green have been tested.
- javax.el-api 3.0.0 (Expression Language 3.0 API)
- javax.el 2.2.6 (Expression Language 2.2 Implementation)
- JDK 8
- Maven 3.8.1
|