JavaBean Validation - Grouping constraints

[Updated: Aug 17, 2017, Created: Sep 26, 2016]

Constraints can be associated with groups.

Each constraint must have an element group=Class<?>[] where group has to be an empty interface, it's a way to specify a strong typed group.

Groups allow us to restrict the set of constraints applied during validation.

When kicking off validation process we can specify which group should be validated by using Validator#validate(T object, Class<?> groups) and other variants of ExecutableValidator#validate(..)

Let's see with an example how it works.



Creating groups

 public interface GroupUserName {}
 public interface GroupAddress {}


Creating bean

  public class User {
    @NotNull(groups = GroupUserName.class)
    String firstName;
    @NotNull(groups = GroupUserName.class)
    String lastName;

    @NotNull(groups = GroupAddress.class)
    String streetAddress;
    @NotNull(groups = GroupAddress.class)
    String country;
    @NotNull(groups = GroupAddress.class)
    @Size(min = 5, groups = GroupAddress.class)
    String zipCode;

    @NotNull
    String userId;

    //getters and setters
  }


Validating for group GroupUserName

    User user = new User();
    user.setFirstName("Jennifer");
    //  user.setLastName("Wilson");

    Set<ConstraintViolation<User>> constraintViolations =
                           validator.validate(user, GroupUserName.class);

    if (constraintViolations.size() > 0) {
          constraintViolations.stream().forEach(
                               ConstraintGroupExample::printError);
    } else {
          //proceed using user object
          System.out.println(user);
    }

Output:

lastName may not be null


Validating for both groups GroupUserName and GroupAddress

    User user = new User();
    user.setFirstName("Jennifer");
    //  user.setLastName("Wilson");

    Set<ConstraintViolation<User>> constraintViolations =
                          validator.validate(user, GroupUserName.class,
                                                      GroupAddress.class);

    if (constraintViolations.size() > 0) {
            constraintViolations.stream().forEach(
                                ConstraintGroupExample2::printError);
    } else {
            //proceed using user object
            System.out.println(user);
    }

Output:

country may not be null
zipCode may not be null
lastName may not be null
streetAddress may not be null


Validating without any group

When not specifying any group, the default group javax.validation.groups.Default is assumed. We have only one such constraint in our User class which is on userId field.

   User user = new User();
   user.setFirstName("Jennifer");
   //  user.setLastName("Wilson");

   Set<ConstraintViolation<User>> constraintViolations =
                                            validator.validate(user);

   if (constraintViolations.size() > 0) {
          constraintViolations.stream().forEach(
                                ConstraintGroupExample3::printError);
   } else {
          //proceed using user object
          System.out.println(user);
   }

Output:

userId may not be null

Note that we can also specify 'Default' group along with other groups:

   validator.validate(user, GroupUserName.class,
                      GroupAddress.class, Default.class);

In this case output will be:

country may not be null
streetAddress may not be null
userId may not be null
lastName may not be null
zipCode may not be null



Group Sequences

By default, constraint groups are validated in no particular order. But we can specify a particular order by defining a new interface which should be annotated with GroupSequence.

This annotation does one more thing, that is, next group will not be validated if current one has validation errors. This is different than previous examples where all groups specified in Validator#validate will be validated together.


Let's continue with our last example and define a new group sequence:

import javax.validation.GroupSequence;
import javax.validation.groups.Default;

@GroupSequence({Default.class, GroupUserName.class, GroupAddress.class})
public interface GroupSequenceForUser {
}

Now groups will be validate in the provided order i.e. first Default group, if there's no error then GroupUserName will be validated, if still no error then GroupAddress will be validated.

To make it work, we have to pass the sequence interface as a group to Validate#validate method:

  User user = new User();
  Set<ConstraintViolation<User>> constraintViolations =
                            validator.validate(user, GroupSequenceForUser.class);

        if (constraintViolations.size() > 0) {
            constraintViolations.stream().forEach(
                                ConstraintGroupSequenceExample::printError);
        } else {
            //proceed using user object
            System.out.println(user);
        }

Output:

userId may not be null


Redefining the default group sequence

We have one more interesting feature related to the group sequence: Instead of creating a new interface annotated with @GroupSequence and defining the sequence, we can use the same @GroupSequence annotation on our bean. In that case the order of the provided groups becomes the 'Default' group which is tied to the target bean class only:

 @GroupSequence({User2.class, GroupUserName.class, GroupAddress.class})
 public class User2 {
    @NotNull(groups = GroupUserName.class)
    String firstName;
    @NotNull(groups = GroupUserName.class)
    String lastName;

    @NotNull(groups = GroupAddress.class)
    String streetAddress;
    @NotNull(groups = GroupAddress.class)
    String country;
    @NotNull(groups = GroupAddress.class)
    @Size(min = 5, groups = GroupAddress.class)
    String zipCode;

    @NotNull
    String userId;

   // getters and setters
}

Since there must not be cyclic dependency in the group, we cannot add 'Default' group while redefining the default group. Instead the class itself can be added to the sequence. In above example User2.class is added in @GroupSequence itself to include the default.


Performing validation:

    User2 user = new User2();
    Set<ConstraintViolation<User2>> constraintViolations =
                                                     validator.validate(user);

    if (constraintViolations.size() > 0) {
            constraintViolations.stream().forEach(
                                RedefiningDefaultGroupExample::printError);
    } else {
            //proceed using user object
            System.out.println(user);
    }

Output:

userId may not be null


Example Project

Dependencies and Technologies Used :

  • Hibernate Validator Engine 5.2.4.Final: Hibernate's Bean Validation (JSR-303) reference implementation.
  • Expression Language API 2.2 2.2.4
  • Expression Language 2.2 Implementation 2.2.4
  • JDK 1.8
  • Maven 3.0.4

Grouping Constraints Select All Download
  • constraint-groups
    • src
      • main
        • java
          • com
            • logicbig
              • example

See Also