JavaBean Validation - Class level constraints

[Updated: Aug 11, 2017, Created: Sep 25, 2016]

We can do validations on all or some of the properties of a bean by placing the constraints on class level.

In following example we are modifying 'constructor return value' example from the last tutorial.


Creating constraint annotation

    @Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Constraint(validatedBy = OrderValidator.class)
    @Documented
    public @interface ValidOrder {
        String message () default "total price must be 50 or greater for online order. " +
                            "Found: ${validatedValue.totalPrice}";
        Class<?>[] groups () default {};
        Class<? extends Payload>[] payload () default {};
    }

As compared to our last tutorial example, this time we used @Target({ElementType.TYPE ..}) instead of ElementType.CONSTRUCTOR. We could have used both, that won't hurt.



Creating validator

It's still the same, no difference.

    public class OrderValidator implements
                            ConstraintValidator<ValidOrder, Order> {
        @Override
        public void initialize (ValidOrder constraintAnnotation) {
        }

        @Override
        public boolean isValid (Order order,
                                       ConstraintValidatorContext context) {
            if (order.getPrice() == null || order.getQuantity() == null) {
                return false;
            }
            return order.getTotalPrice()
                        .compareTo(new BigDecimal(50)) >= 0;

        }
    }


The bean:

This time we put @ValidOrder constraint on class-level instead of on constructor level.

    @ValidOrder
    public class Order {
        private final BigDecimal price;
        private final BigDecimal quantity;

        public Order (BigDecimal price, BigDecimal quantity) {
            this.price = price;
            this.quantity = quantity;
        }

        public BigDecimal getPrice () {
            return price;
        }

        public BigDecimal getQuantity () {
            return quantity;
        }

        public BigDecimal getTotalPrice () {
            return (price != null && quantity != null ?
                                price.multiply(quantity) : BigDecimal.ZERO)
                                .setScale(2, RoundingMode.CEILING);
        }
    }

Performing validation:

We are not going to use ExecutableValidator because we are not performing validation on an executable (i.e. constructor invocation). This time we are just using Validator

   Order order = new Order(new BigDecimal(4.5), new BigDecimal(9));
   Set<ConstraintViolation<Order>> constraintViolations =
                                               validator.validate(order);

        if (constraintViolations.size() > 0) {
            constraintViolations.stream().forEach(
                                ClassLevelConstraintExample::printError);
        } else {
            //proceed using order
            System.out.println(order);
        }
    }
    

Output:

 total price must be 50 or greater for online order. Found: 40.50




Difference between 'constructor return value' and class-level validation

Unlike constructor return value validation, class-level validation is not tied to a constructor invocation.

Class level validation performs validation like this:

validator.validate(object);

Whereas, constructor return value performs validation like this:

  ExecutableValidator executableValidator = validator.forExecutables();
  executableValidator.validateConstructorReturnValue(constructor, object);

In the 'constructor-return-value' case, that is developer responsibility to tie the correct constructor to the new Object . The constructor in above snippet (which is the instance of java.lang.reflect.Constructor) should be the same which created the object, otherwise validation won't be performed. Also the corresponding validator implementation's isValid(..) method should do the checking on the same bean properties or fields which have been initialized with the target constructor. So the constructor invocation to create the object, calling executableValidator.validateConstructorReturnValue(..) and isValid(..) implementation should be consistent to each other.

Constructor return value concept is to test that the bean has been initialized as intended via a constructor invocation. A bean can have multiple constructors and they are free to initialize the object fields differently.

Class-level validation is tied to the newly created object regardless of what constructor is used.




Example Project

Dependencies and Technologies Used :

  • Hibernate Validator Engine 5.2.4.Final: Hibernate's Bean Validation (JSR-303) reference implementation.
  • Expression Language API 2.2 2.2.4
  • Expression Language 2.2 Implementation 2.2.4
  • JDK 1.8
  • Maven 3.0.4

Class Level Constraints Select All Download
  • class-level-constraint
    • src
      • main
        • java
          • com
            • logicbig
              • example

See Also