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 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
|