Hi everyone,
Several questions have been raised around groups recently. I have adjusted the spec to answer them. Can you review the wording (esp the addition to the formal rules)? I put changes in red.

The idea is:
 - an interface representing a group sequence cannot have inherited groups
 - a constraint declaration cannot use a group reprensenting a group sequence
 - when a group A is sequenced before B, all groups composing A (via inheritance or group sequence) are sequenced before B
 - if a circularity arise, an exception happens



3.4. Group and group sequence

A group defines a subset of constraints. Instead of validating all constraints for a given object graph, only a subset is validated depending on the group targeted. Each constraint declaration defines the list of groups it belongs to. If no group is explicitly declared, a constraint belongs to the Default group.

Groups are represented by interfaces.

Example 3.1. Definition of groups

/**
 * Validation group checking a user is billable
 */
public interface Billable {}

/**
 * customer can buy without harrassing checking process
 */
public interface BuyInOneClick {
}

A constraint can belong to one or more groups.

Example 3.2. Assign groups to constraints

/**
 * User representation
 */
public class User {
    @NotNull
    private String firstname;

    @NotNull(groups = Default.class)
    private String lastname;

    @NotNull(groups = {Billable.class, BuyInOneClick.class})
    private CreditCard defaultCreditCard;
}

During the validation call, one or more groups are validated. All the constraints belonging to this set of group is evaluated on the object graph. In Example 3.2, “Assign groups to constraints”,@NotNull is checked on defaultCreditCard when either the Billable or BuyInOneClick group is validated. @NotNull on firstname and on lastname are validated when the Default group is validated. Reminder: constraints held on superclasses and interfaces are considered.

Default is a group predefined by the specification

package javax.validation.groups;

/**
 * Default Bean Validation group
 *
 * @author Emmanuel Bernard
 */
public interface Default {
}

3.4.1. Group inheritance

In some situations, a group is a super set of one or more groups. This can be described by Bean Validation. A group can inherit one or more groups by using interface inheritance.

Example 3.3. Groups can inherit other groups

/**
 * Customer can buy without harrassing checking process
 */
public interface BuyInOneClick extends Default, Billable {
}

For a given interface Z, constraints marked as belonging to the group Z (ie where the annotation groups property contains the interface Z) or any of the super interfaces of Z (inherited groups) are considered part of the group Z.

In the following example:

Example 3.4. Use of a inherited group

/**
 * User representation
 */
public class User {
    @NotNull
    private String firstname;

    @NotNull(groups = Default.class)
    private String lastname;

    @NotNull(groups = {Billable.class})
    private CreditCard defaultCreditCard;
}

validating the group BuyInOneClick will lead to the following constraints checking:

  • @NotNull on firstname and lastname

  • @NotNull on defaultCreditCard

because Default and Billable are subinterfaces of BuyInOneClick.

3.4.2. Group sequence

By default, constraints are evaluated in no particular order and this regardless of which groups they belong to. It is however useful in some situations to to control the order of constraints evaluation. There are often scenarios where a preliminary set of constraints should be evaluated prior to other constraints. Here are two examples:

  • The second group depends on a stable state to run properly. This stable state is verified by the first previous group.

  • The second group is a heavy consumer of time, CPU or memory and its evaluation should be avoided if possible.

To implement such ordering, a group can be defined as a sequence of other groups. Each group in a group sequence must be processed sequentially in the order defined by@GroupSequence.value when the group defined as a sequence is requested. Note that a group member of a sequence can itself be composed of several groups via inheritance or sequence definition. In this case, each composed group must respect the sequence order as well.

Processing a group is defined in Section 3.5, “Validation routine” ; if one of the groups processed in the sequence generates one or more constraint violation, the groups following in the sequence must not be processed. This ensure that a set of constraint is evaluated only if another set of constraint is valid.

Groups defining a sequence and groups composing a sequence must not be involved in a cyclic dependency either directly or indirectly, either through cascaded sequence definition or group inheritance.

Groups defining a sequence should not directly inherit other groups. In other words, the interface hosting the group sequence should not have any super interface.

Groups defining a sequence should not be used directly in constraint declarations. In other words, the interface hosting the group sequence should not be used in a constraint declaration.

To define a group as a sequence, the interface must be annotated with the @GroupSequence annotation.

@Target({TYPE})
@Retention(RUNTIME)
public @interface GroupSequence {
    Class<?>[] value();
}

Here is a usage example

Example 3.5. Make use of group sequence

@ZipCodeCoherenceChecker(groups = Address.HighLevelCoherence.class)
public class Address {
    @NotNull @Size(max = 50)
    private String street1;

    @ZipCode
    private String zipcode;

    @NotNull @Size(max = 30)
    private String city;

    /**
     * check conherence on the overall object
     * Needs basic checking to be green first
     */
    public interface HighLevelCoherence {}

    /**
     * check both basic constraints and high level ones.
     * high level constraints are not cheked if basic constraints fail
     */
    @GroupSequence({Default.class, HighLevelCoherence.class})
    public interface Complete {}
}

In Example 3.5, “Make use of group sequence”, when the Address.Complete group is validated, all constraints belonging to the Default group are validated. If any of them fail, the validation skips the HighLevelCoherence group. If all Default constraints pass, HighLevelCoherence constraints are evaluated.

Note

A given constraint can belong to two groups ordered by a sequence. In this case, the constraint is evaluated as part of the first group and ignored in the subsequent group(s). SeeSection 3.5, “Validation routine” for more informations.

3.4.3. Redefining the Default group for a class

In Example 3.5, “Make use of group sequence”, validating the Default group does not validate HighLevelCoherence constraints. To ensure a complete validation, a user must use the Completegroup. This breaks some of the encapsulation you could expect. You can work around this by redefining what the Default group means for a given class. To redefine Default for a class, place a@GroupSequence annotation on the class ; this sequence expresses the sequence of groups that does substitute Default for this class.

Example 3.6. Redefining Default group for Address

@GroupSequence({Address.class, HighLevelCoherence.class})
@ZipCodeCoherenceChecker(groups = Address.HighLevelCoherence.class)
public class Address {
    @NotNull @Size(max = 50)
    private String street1;

    @ZipCode
    private String zipcode;

    @NotNull @Size(max = 30)
    private String city;

    /**
     * check conherence on the overall object
     * Needs basic checking to be green first
     */
    public interface HighLevelCoherence {}
}

In Example 3.6, “Redefining Default group for Address”, when an address object is validated for the group Default, all constraints belonging to the group Default and hosted on Address are evaluated. If none fails, all HighLevelCoherence constraints present on Address are evaluated. In other words, when validating the Default group for Address, the group sequence defined on the Address class is used.

Since sequences cannot have circular dependencies, using Default in the declaration of a sequence is not an option. Constraints hosted on a class A and belonging to the Default group (by default or explicitly) implicitly belong to the group A.

A sequence defined on a class A (ie. redefining the Default groups for the class) must contain the group A. In other words, the default constraints hosted on a class must be part of the sequence definition.

3.4.4. Implicit grouping

It is possible to implicitly group some constraints in the same group without explicitly listing such a group in the constraint declaration. Every constraint hosted on an interface Z and part of theDefault group (implicitly or explicitly) belongs to the group Z. This is useful to validate the partial state of an object based on a role represented by an interface.

Example 3.7. Example of interface / group hosting constraints

/**
 * Auditable object contract
 */
public interface Auditable {
    @NotNull String getCreationDate();
    @NotNull String getLastUpdate();
    @NotNull String getLastModifier();
    @NotNull String getLastReader();
}

/**
 * Represents an order in the system
 */
public class Order implements Auditable {
    private String creationDate;
    private String lastUpdate;
    private String lastModifier;
    private String lastReader;

    private String orderNumber;

    public String getCreationDate() {
        return this.creationDate;
    }

    public String getLastUpdate() {
        return this.lastUpdate;
    }

    public String getLastModifier() {
        return this.lastModifier;
    }

    public String getLastReader() {
        return this.lastReader;
    }

    @NotNull @Size(min=10, max=10)
    public String getOrderNumber() {
        return this.orderNumber;
    }
}

When an Order object is validated on the Default group, the following constraints are validated: @NotNull on getCreationDategetLastUpdategetLastModifiergetLastReader,getOrderNumber and @Size on getOrderNumber as all belong to the Default group.

When an Order object is validated on the Auditable group, the following constraints are validated: @NotNull on getCreationDategetLastUpdategetLastModifiergetLastReader. Only the constraints present on Auditable (and any of its super interfaces) and belonging to the Default group are validated when the group Auditable is requested. It allows the caller to validate that a given object can be safely audited even if the object state itself is not valid.

3.4.5. Formal group definitions

The formal rules defining groups are as followed. Text in italic are comments about the rules.

For every class X:

  • A. For each superclass Y of X, the group Y contains all constraints of the group Y of Y

    this rule prepares formal concepts for recursive discovery

  • B. The group X contains the following constraints:

    group X is a group used on sequences redefining the default group on a class (see Section 3.4.3, “Redefining the Default group for a class”)

    1. every constraint declared by the class X which does not declare a group or does declare the group Default explicitly.

      all Default constraints hosted on X

    2. every constraint declared by any interface implemented by X and not annotated @GroupSequence which does not explicitly declare a group or does declare the group Default explicitly.

      all Default constraints hosted on interfaces of X: constraints are inherited by the class hierarchy. Interfaces marked as @GroupSequence are ignored.

    3. if X has a direct superclass Y, every constraint in the group Y

      all Default constraints hosted on the superclasses of X: constraints are inherited by the class hierarchy

  • C. If X has no @GroupSequence annotation, the group Default contains the following constraints:

    this rule defines which constraints are evaluated when validating Default on X.

    1. every constraint in the group X

    2. if X has a direct superclass Y, every constraint in the group Default of Y

      this rule is necessary in case Y redefines the group Default

  • D. If X does have a @GroupSequence annotation, the group Default contains every constraint belonging to every group declared by the @GroupSequence annotation.

    this rule describes how a class can redefine the group Default for itself (see Section 3.4.3, “Redefining the Default group for a class”)

    • the @GroupSequence annotation must declare the group X

  • E. For every interface Z, the group Z contains the following constraints:

    this rule defines how non Default groups are defined

    1. every constraint declared by the interface Z which does not explicitly declare a group or does declare the group Default explicitly.

      all Default constraints hosted on Z: this rule formally defines implicit grouping per interface (see Section 3.4.4, “Implicit grouping”)

    2. every constraint declared by any superinterface not annotated @GroupSequence of the interface Z which does not explicitly declare a group

      all Default constraints hosted on interfaces of Z: groups can be inherited (see Section 3.4.1, “Group inheritance”)

    3. every constraint declared by the class X which explicitly declares the group Z

      every constraint hosted by X and marked as belonging to the group Z

    4. every constraint declared by any interface implemented by X and not annotated @GroupSequence which explicitly declares the group Z

      every constraint hosted by any interface of X and marked as belonging to the group Z

    5. if X has a direct superclass Y, every constraint in the group Z of Y

      every constraint hosted by any superclass of X and marked as belonging to the group Z

  • F. For every interface Z annotated @GroupSequence, the group Z contains every constraint belonging to every group declared by the @GroupSequence annotation.

    defines the composition side of group sequence but does not define the ordering behavior of sequence (see Section 3.4.2, “Group sequence”)


When a given group G (represented by an interface G) is requested for the validation of a class X:

  • constraints belonging to the group G are evaluated

  • if the interface G is not annotated @GroupSequence, every group represented by the super interface of G are requested for validation

  • if the interface G is annotated with @GroupSequence, every group represented by the interfaces declared by the @GroupSequence annotation are requested for validation

    • the validation of groups declared to the @GroupSequence must happen in the sequencing order declared by @GroupSequence: the sequencing order is propagated to the groups composing the sequenced group (via inheritance or group sequence)

    • if a group validation triggers the failure of one or more constraints, groups following in the sequence must not be evaluated.

  • if the group G represents the Default group of X overridden by @GroupSequence, operations are equivalent

When the Default group of a given class X is overridden via @GroupSequence, its validation is as followed:

  • every group represented by the interfaces declared by the @GroupSequence annotation are requested for validation

    • the validation of groups declared to the @GroupSequence must happen in the sequencing order declared by @GroupSequence: the sequencing order is propagated to the groups composing the sequenced group (via inheritance or group sequence)

    • if a group validation triggers the failure of one or more constraints, groups following in the sequence must not be evaluated.

Unless defined by a @GroupSequence, evaluation ordering is not constrained. In particular, several groups can be validated in the same pass. If a group definition leads to a circular sequencing order between groups, an exception is raised.

Note

A group G sequenced (directly or indirectly) to be executed before itself is not considered a circular reference.