This is the third installment of a series of blog entries on the Bean Validation specification (JSR 303). If you have not done it yet, go read the first and second part.

The Bean Validation specification let's you define constraints on your domain model. In a couple of situations, validating a subset of constraints is needed.

In a wizard-style screen, the information is partially distilled over time from screen to screen. Validating data for each screen is useful but as a whole, the data is not ready for a complete constraint validation.

Some constraints need to run after others. There are several use cases for ordering constraints:

  • higher level constraints may expect data pre validated by basic constraints before being triggered themselves
  • some constraints are expensive in CPU, memory, time or even money. Running the previous constraints first and fail fast is required
  • some use cases need to validate additional or specific constraints not necessary for others use cases

To solve this class of problems, the Bean Validation specification uses the notion of group. Each constraint can define a set of groups it belongs to. When it comes to validate an object graph, one or several groups can be requested.

The principles

Let's first describe the theory behind groups

Declare a constraint as belonging to a group

Each constraint has a groups element whose default value is an empty array. When this element is not explicitly declared on an constraint annotation, the default group is assumed

public class Address {
        //not null belongs to the "default" group
        @NotNull String street1;
}

Any constraint declaration can define one or several groups it belongs to. Note that default is the special default group.

public class Address {
        @NotNull(groups={"basic", "default"}) private String street1;
}

In this example, @NotNull is checked any time either the basic or default group is requested.

Defining constraint ordering

Ordering constraints for the sake of ordering the execution does not make a lot of sense since the Bean Validation implementation ends up executing all the constraints and return all the failures (for a given set of groups). However, it is useful to have the ability to avoid the execution of a constraint if an other constraint fails.

The Bean Validation specification uses the notion of a group sequence. A group sequence declares a list of groups. Each group must be validated sequentially following the list order. Validating a group means validating all constraints of an object graph belonging to the group. If one or more constraints fail in a given group, the following groups in the group sequence are not processed.

A group sequence is declared with @GroupSequence and is applied on the class it is set on.

@GroupSequence(name="default", sequence={"basic", "complex"})
@AddressChecker(groups="complex")
public class Address {
        @NotNull(groups="basic") private String street1;
        ...
}

In an object graph (@Valid), group sequences from the parent object are inherited by the children objects. @GroupSequences is used to declare more than one sequence per class. A group sequence can contain the name of another group sequence.

Validating a group

By default, the validate method validates the default group. You can decide to validate one or several groups:

validator.validate(address, "basic");
validator.validate(address, "firstscreen", "secondscreen");

The groups passed to the validate method are not processed in a particular order (if you need ordering, use a group sequence).

Practical uses

Enough theory, let's see how groups and group sequences solve our use cases.

Use case specific validation

Defining constraints based on specific use cases is sometimes useful. A typical example comes from reusing the same object model for different purposes, in different contexts.

In our example, an account can be in three states, default, canbuy and oneclick. A canbuy account has enough information to buy items, a oneclick account can buy in... ahem one click.

public class Account {

        @NotEmpty @Length(max=50) private String firstname;
        @Length(max=50) private String middleName;
        @NotEmpty @Length(max=50) private String lastname;
        
        @NotNull(groups="canbuy") @Phone private String cellPhone;

        @Valid @NotEmpty(groups="canbuy")
        private Set<Address> addresses;

        @Valid @NotNull(groups="canbuy")
        private Address defaultAddress;
        
        @Valid @NotEmpty(groups="canbuy")
        private Set<PaymentMethod> paymentMethod;

        @Valid @NotNull(groups="oneclick")
        private Address defaultPaymentMethod;

}

Let's dissect the previous example. A default account must have a first name and last name. If it has addresses, payment methods and phone number, each of these properties must be valid. However, these properties do not have to be filled up to be valid.

An account can buy if it has a default address and at least one payment method. An account can buy in one click if it has a default payment method.

We could imagine the following logic

Set<InvalidConstraint> accountIssues = validator.validate(account);
if ( accountIssues.size() > 0 ) {
        //push that error list to the user
}
else {
        if ( validator.validate(account, "canbuy").size() == 0 ) {
                enableBuyButton();
                if ( validator.validate(account, "oneclick").size() == 0 ) {
                        enableOneClick();
                }
        }
}

The data consistency is ensured by the default group. And some additional state checking is provided by additional groups. Note that in an integrated world, your application framework or your web framework would let your declaratively express which group needs to be validated in a given page context, page, conversation etc. The application avoids the extra work of manually executing the validation.

Partial data validation: the wizard screen model

In a wizard-style user interface, data is provided partially and gets more and more complete when the user move from one wizard screen to the other.

Let's take the previous example and let's imagine a wizard building a one click account from the ground up:

  • the first screen acquires the firstname, lastname and phone
  • the second screen adds one or several addresses
  • the third screen adds one or several payment methods
  • the forth screen makes sure the user selects a default address and payment method and that everything is set up for a one click process
  • the fifth screen is a summary and make sure that everything is in place
public class Account {

        @NotEmpty(groups={"firstscreen", "default"}) 
        @Length(max=50, groups={"firstscreen", "default"}) 
        private String firstname;
        
        @NotEmpty(groups={"firstscreen", "default"})
        @Length(max=50, groups={"firstscreen", "default"}) 
        private String lastname;
        
        @NotNull(groups={"firstscreen", "canbuy"}) 
        @Phone(groups={"firstscreen", "default"})
        private String cellPhone;

        @Valid @NotEmpty(groups={"secondscreen", "canbuy"})
        private Set<Address> addresses;

        @Valid @NotNull(groups={"forthscreen", "canbuy"})
        private Address defaultAddress;
        
        @Valid @NotEmpty(groups={"thirdscreen", "canbuy"})
        private Set<PaymentMethod> paymentMethod;

        @Valid @NotNull(groups={"forthscreen", "oneclick"})
        private Address defaultPaymentMethod;

}

For each screen but the last, a specific group has been set up. The last screen needs to ensure an account is valid for a oneclick operation. It then needs to validate default, canbuy and oneclick groups.

While the group validation can be called programmatically, we expect application frameworks and web frameworks to let you define the targeted groups declaratively.

In JSF, it could look like:

<s:validateAll groups="firstscreen">
  <s:decorate>
    <h:inputText id="firstname" value="#{account.firstname}" />
    <h:message for="firstname" styleClass="error" />
    <br/>
    <h:inputText id="lastname" value="#{account.lastname}" />
    <h:message for="lastname" styleClass="error" />
    <br/>
    <h:inputText id="cellPhone" value="#{account.cellPhone}" />
    <h:message for="cellPhone" styleClass="error" />
  </s:decorate>
</s:validateAll>

This is just a proposal / vision. This integration is not part of the JSR 303 specification.

Constraint ordering

An other use case is the ability to stop constraint validations if a group of constraint fails. It is quite useful in (at least) two situations:

  • some constraint validations (especially class level constraints) expect to work on pre-validated constraints (for example pre validated with nullability, length and global pattern matchings): the basic level constraints must be valid before calling a higher level constraint checking.
  • some constraints are expensive to run (long processing, access an external resource etc)

Validating an address can be fairly simple (some basic constraints) especially when targeting one country. It could also be much more complex and involve address coherence checking (the zipcode must match the city, the street name much match the zipcode and so on). The address coherence checking cannot really be applied until we are sure the basic constraints are valid as it expects some pre normalized data. On top of that, our application has to call an external service to check the coherence. And it turns out calling this service has two drawbacks:

  • it is a bit slow (at least slower than the other constraint validations)
  • it costs a fee per execution

We want to make sure the address coherence constraint is not called unless the other constraints are valid:

@GroupSequence(name="default", sequence={"basic", "complex"})
@Address(groups="complex")
public class Address {
    @NotNull(groups="basic") @Max(50, groups="basic")) private String street1;
    @Max(50, groups="basic")) private String street2;
    @Max(10, groups="basic")) @NotNull(groups="basic") private String zipCode;
    @Max(20, groups="basic")) @NotNull(groups="basic") String city;
    @NotNull(groups="basic") private Country country;
    
    ...
}

The group sequence will first validate the constraints marked as basic, then validate the constraints marked as complex unless one (or more) constraint from the basic group failed.

Conclusion

In most cases, the default group will suffice. But more complex use cases need additional flexibility in the constraint definition and validation. While declaring groups is the application developers responsibility, validating a given set of groups will most likely be driven by the client framework (application frameworks, web frameworks and to a certain extends persistence frameworks). Either by using method annotations or template declarations, frameworks like WebBeans or JSF will capture the requested constraint validation groups and execute the validation calls for the application.

This part of the specification is probably the most controversial. The current proposal tries to achieve several goals:

  • embrace the declarative model
  • stay as simple and understandable as possible
  • provide enough flexibility to match most use cases

The expert group is seeking feedback in several forms on the groups approach:

  • use cases: describe your use cases, see how (or if) they can be addressed by the current proposal
  • enhancements: propose enhancements on the groups concept to make it more flexible or more simple
  • alternatives: you think you have the perfect solution? go describe it, let's see how it fits the model

We encourage you to read the specification and provide feedbacks and comments on this forum.


Back to top