JavaBean Validation - Using constraints on constructor and method return values

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

Using constraints on method return values

We can validate method return value to make sure that all post condition are met after calling the method.


The bean:

    public class TestBean {

        @Size(min = 5)
        public int[] findNumbers () {
            return new int[]{1, 2, 3};
        }
    }

Performing validation:

    TestBean test = new TestBean();
    int[] testNumbers = test.findNumbers();

    Method method = TestBean.class.getDeclaredMethod("findNumbers");

    ExecutableValidator executableValidator = validator.forExecutables();
    Set<ConstraintViolation<TestBean>> violations =
                            executableValidator.validateReturnValue(test,
                                                method, testNumbers);

    if (violations.size() > 0) {
        violations.stream().forEach(MethodReturnValidationExample::printError);
    } else {
        //proceed using TestBean object
    }

Output:

  getNumbers.<return value> size must be between 5 and 2147483647




Using constraints on constructor return value.

Constructors don't return values like method do but they return a newly instantiated object. We can find out validation violation on new object to make sure that all post conditions are met after the object creation.

Following example creates a custom constraints 'ValidOrder' to validate the returned constructor value. The assumed requirement in this example is: the total order prices should be $50 or more.


The bean

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

        @ValidOrder
        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);
        }
    }

The constraint annotation:

    @Target({ElementType.CONSTRUCTOR, 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 {};
    }

The 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

    Order order = new Order(new BigDecimal(4.5), new BigDecimal(10));
    Constructor<Order> constructor =
                Order.class.getConstructor(BigDecimal.class, BigDecimal.class);

    ExecutableValidator executableValidator = validator.forExecutables();
    Set<ConstraintViolation<Order>> constraintViolations =
                executableValidator.validateConstructorReturnValue(constructor, order);

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

Note: we specifically used ExecutableValidator#validateConstructorReturnValue in above example for constructor return value validation.


Output:
    Order.<return value> total price must be 50 or greater for online order. Found: 45.00




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

Return Value Constraints Select All Download
  • return-value-validation
    • src
      • main
        • java
          • com
            • logicbig
              • example

See Also