Close

Java Bean Validation - Class level constraints

[Last Updated: Aug 25, 2021]

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

In this example we are going create a custom constraint 'ValidOrder' to validate if the bean property, Order#price, is $50 or more


Example

package com.logicbig.example;

import javax.validation.*;
import java.lang.annotation.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Set;

public class ClassLevelConstraintExample {

    //using our custom constraint on class level
    @ValidOrder
    public static 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);
        }
    }

    //Our constraint definition
    @Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Constraint(validatedBy = OrderValidator.class)
    @Documented
    public static @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 {};
    }

    //our validator
    public static 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;
        }
    }

    //performing validation
    public static void main (String[] args)  {
        Order order = new Order(new BigDecimal(4.5), new BigDecimal(9));

        Validator validator = getValidator();
        Set<ConstraintViolation<Order>> constraintViolations = validator.validate(order);

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

    private static Validator getValidator(){
        Configuration<?> config = Validation.byDefaultProvider().configure();
        ValidatorFactory factory = config.buildValidatorFactory();
        Validator validator = factory.getValidator();
        factory.close();
        return validator;
    }

    private static void printError (
                        ConstraintViolation<Order> violation) {
        System.out.println(violation.getPropertyPath() + " " + violation.getMessage());
    }
}

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#isValid(..) implementation 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 6.2.0.Final (Hibernate's Jakarta Bean Validation reference implementation)
     Version Compatibility: 5.0.0.Final - 6.2.0.Final Version List
    ×

    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

Class level constraints Example Select All Download
  • class-level-constraint
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • ClassLevelConstraintExample.java

    See Also