Monday, 1 August 2011

Using JSR 303 Validation Groups

Anyone who’s looked at the code for a JSR 303 custom constraint will have noticed that the annotation interface declaration contains a couple of less than obvious attributes: groups() and payload as demonstrated by the @Country custom constraint below:

@Target({ ElementType.METHOD, ElementType.FIELD })
@Constraint(validatedBy = CountryValidator.class)
public @interface Country {

String message() default "{country.constraint}";

[] values() default "UK";

[] groups() default {};

extends Payload>[] payload() default {};

This blog covers the groups attribute in an attempt to explain its purpose.

Given a JavaBean, then JSR 303 groups allow you to test your bean in different states. For example, at point A in your code your bean will be in a certain state and can be validated against constraints X. Later, at point B in your code, your bean will be in a different state and can be validated against constraints Y. Confused? The following code may help....

public class Address {

(min = 2, max = 25)
private String street;

private String town;

@BirminghamPostCode(message = "The postcode must start with 'B'", groups = { AddressGroup.class })
private String postCode;

@Country(values = { "UK", "US", "FR", "DE", "ES", "PL", "AU" }, message = "Invalid Country Code", groups = AddressGroup.class)
private String country;
// Getters and setters go here....

As you can see, the Address bean has four properties: street, town, postcode and country. street and town do not have the groups attribute set. They are part of what’s known as the default group. postcode and country are part of the AddressGroup.

AddressGroup is nothing more than and empty marker or tag interface, like Serialize or Cloneable; it’s there to tell the validator to which group the constraint belongs.

The code below is a simple unit test that demonstrates how groups are managed by the validator:

public void demonstrateGroups() {

ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
    validator = factory.getValidator
    Address address =
new Address();
("Gold Avenue");
("B15 1NP");
("Invalid Country Code");

    Set<ConstraintViolation<Address>> constraintViolations = validator.validate

if (constraintViolations.isEmpty()) {
constraintViolations = validator.validate(address, AddressGroup.class);
(1, constraintViolations.size());

The code above first creates a validator and the Address test bean. The next two lines set up the Address bean leaving it in an incomplete state. The validator is then applied to the bean using the default groups value. This will apply the @NotEmpty, @Size(...) and @NotNull constraints of the default group in the Address class code above and all will pass.

The state of the Address bean is then altered by updating the postCode and country properties. The validator is then called using groups=AddressGroup.class applying the @BirminghamPostCode and @Country constraints to the postCode and country properties of the Address class respectively. In this test the @BirminghamPostCode constraint will pass and the @Country constraint will fail as the country code is invalid.

And that's all there is to it. The code applied two groups to an instance of a bean that's in different states at different times.

For more information on the @Country and @BirminghamPostCode custom constraints see previous blogs.

For more information this from Hiberate, take a look at their documentation.

1 comment:

Rohit Salecha said...

Hi Captiain , Excellent Posts !

Just a suggestion to add another Post on Cross-field validations as given here.

You can refine it further !