For multiple properties validation, you should use class-level constraints. From
Bean Validation Sneak Peek part II: custom constraints:
Class-level constraints
Some of you have expressed concerns
about the ability to apply a
constraint spanning multiple
properties, or to express constraint
which depend on several properties.
The classical example is address
validation. Addresses have intricate
rules:
- a street name is somewhat standard and must certainly have a length limit
- the zip code structure entirely depends on the country
- the city can often be correlated to a zipcode and some error checking can
be done (provided that a validation
service is accessible)
- because of these interdependencies a simple property level constraint does
to fit the bill
The solution offered by the Bean
Validation specification is two-fold:
- it offers the ability to force a set of constraints to be applied before an
other set of constraints through the
use of groups and group sequences.
This subject will be covered in the
next blog entry
- it allows to define class level constraints
Class level constraints are regular
constraints (annotation /
implementation duo) which apply on a
class rather than a property. Said
differently, class-level constraints
receive the object instance (rather
than the property value) in isValid
.
@Address
public class Address {
@NotNull @Max(50) private String street1;
@Max(50) private String street2;
@Max(10) @NotNull private String zipCode;
@Max(20) @NotNull String city;
@NotNull private Country country;
...
}
@ConstraintValidator(MultiCountryAddressValidator.class)
@Target(TYPE)
@Retention(RUNTIME)
public @interface Address {
String message() default "{error.address}";
String[] groups() default {};
}
public class MultiCountryAddressValidator implements Constraint<Address> {
public void initialize(Address constraintAnnotation) {
// initialize the zipcode/city/country correlation service
}
/**
* Validate zipcode and city depending on the country
*/
public boolean isValid(Object object) {
if (!(object instanceof Address)) {
throw new IllegalArgumentException("@Address only applies to Address");
}
Address address = (Address) object;
Country country = address.getCountry();
if (country.getISO2() == "FR") {
// check address.getZipCode() structure for France (5 numbers)
// check zipcode and city correlation (calling an external service?)
return isValid;
} else if (country.getISO2() == "GR") {
// check address.getZipCode() structure for Greece
// no zipcode / city correlation available at the moment
return isValid;
}
// ...
}
}
The advanced address validation rules
have been left out of the address
object and implemented by
MultiCountryAddressValidator
. By
accessing the object instance, class
level constraints have a lot of
flexibility and can validate multiple
correlated properties. Note that
ordering is left out of the equation
here, we will come back to it in the
next post.
The expert group has discussed various
multiple properties support
approaches: we think the class level
constraint approach provides both
enough simplicity and flexibility
compared to other property level
approaches involving dependencies.
Your feedback is welcome.