Close

Java Bean Validation - Grouping constraints

[Last Updated: Aug 27, 2021]

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. A group defines a subset of constraints. Instead of validating all constraints for a given bean, only a subset is validated.

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(..)

If no group is explicitly declared, a constraint belongs to the javax.validation.groups.Default group.

Let's see with an example how it works.


Examples


Creating groups

package com.logicbig.example;

public interface GroupUserName {
}
package com.logicbig.example;

public interface GroupAddress {
}

Example bean

package com.logicbig.example;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

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;
    .............
}

Validating for group GroupUserName

package com.logicbig.example;

import javax.validation.*;
import java.util.Set;

public class ConstraintGroupExample {
    private static final Validator validator;

    static {
        Configuration<?> config = Validation.byDefaultProvider().configure();
        ValidatorFactory factory = config.buildValidatorFactory();
        validator = factory.getValidator();
        factory.close();
    }

    public static void main (String[] args) {
        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);
        }
    }

    private static void printError (ConstraintViolation<User> violation) {
        System.out.println(violation.getPropertyPath() + " " + violation.getMessage());
    }
}

Output

lastName must not be null


Validating for both groups along with the Default one

package com.logicbig.example;

import javax.validation.*;
import javax.validation.groups.Default;
import java.util.*;
import java.util.stream.Collectors;

public class ConstraintGroupExample2 {
    private static final Validator validator;

    static {
        Configuration<?> config = Validation.byDefaultProvider().configure();
        ValidatorFactory factory = config.buildValidatorFactory();
        validator = factory.getValidator();
        factory.close();
    }

    public static void main(String[] args) {
        User user = new User();
        user.setFirstName("Jennifer");
        //  user.setLastName("Wilson");

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

        if (constraintViolations.size() > 0) {
            Map<String, List<ConstraintViolation<User>>> groupViolationsMap =
                    constraintViolations.stream().collect(
                    Collectors.groupingBy(v ->
                            v.getConstraintDescriptor().getGroups().iterator()
                             .next().getSimpleName(), TreeMap::new, Collectors.toList()));

            groupViolationsMap.forEach((k, v) -> {
                System.out.printf("%n-- Group: %s --%n", k);
                v.stream().sorted(Comparator.comparing(o -> o.getPropertyPath().toString()))
                 .forEach(ConstraintGroupExample2::printError);

            });
        } else {
            //proceed using user object
            System.out.println(user);
        }
    }

    private static void printError(ConstraintViolation<User> violation) {
        System.out.println(violation.getPropertyPath() + " " + violation.getMessage());
    }
}

Output


-- Group: Default --
userId must not be null

-- Group: GroupAddress --
country must not be null
streetAddress must not be null
zipCode must not be null

-- Group: GroupUserName --
lastName must 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.

package com.logicbig.example;

import javax.validation.*;
import java.util.Set;

public class ConstraintGroupExample3 {
    private static final Validator validator;

    static {
        Configuration<?> config = Validation.byDefaultProvider().configure();
        ValidatorFactory factory = config.buildValidatorFactory();
        validator = factory.getValidator();
        factory.close();
    }

    public static void main (String[] args) {
        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);
        }
    }

    private static void printError (ConstraintViolation<User> violation) {
        System.out.println(violation.getPropertyPath() + " " + violation.getMessage());
    }
}

Output

userId must 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:

package com.logicbig.example;

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:

package com.logicbig.example;

import javax.validation.*;
import java.util.Set;

public class ConstraintGroupSequenceExample {
    private static final Validator validator;

    static {
        Configuration<?> config = Validation.byDefaultProvider().configure();
        ValidatorFactory factory = config.buildValidatorFactory();
        validator = factory.getValidator();
        factory.close();
    }

    public static void main (String[] args) {
        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);
        }
    }

    private static void printError (ConstraintViolation<User> violation) {
        System.out.println(violation.getPropertyPath() + " " + violation.getMessage());
    }
}

Output

userId must 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:

package com.logicbig.example;

import javax.validation.GroupSequence;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.validation.groups.Default;

@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;
    .............
}

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:

package com.logicbig.example;

import javax.validation.*;
import javax.validation.groups.Default;
import java.util.Set;

public class RedefiningDefaultGroupExample {
    private static final Validator validator;

    static {
        Configuration<?> config = Validation.byDefaultProvider().configure();
        ValidatorFactory factory = config.buildValidatorFactory();
        validator = factory.getValidator();
        factory.close();
    }

    public static void main (String[] args) {
        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);
        }
    }

    private static void printError (ConstraintViolation<User2> violation) {
        System.out.println(violation.getPropertyPath() + " " + violation.getMessage());
    }
}

Output

userId must not be null

Example Project

Dependencies and Technologies Used:

  • hibernate-validator 6.2.0.Final (Hibernate's Jakarta Bean Validation reference implementation)
     Version Compatibility: 5.0.0.Final - 6.2.0.Final Version List
    ×

    Version compatibilities of hibernate-validator with this example:

      groupId: org.hibernate
      artifactId: hibernate-validator
      Reference implementation for Bean Validation 1.1
    • 5.0.0.Final
    • 5.0.1.Final
    • 5.0.2.Final
    • 5.0.3.Final
    • 5.1.0.Final
    • 5.1.1.Final
    • 5.1.2.Final
    • 5.1.3.Final
    • 5.2.0.Final
    • 5.2.1.Final
    • 5.2.2.Final
    • 5.2.3.Final
    • 5.2.4.Final
    • 5.2.5.Final
    • 5.3.0.Final
    • 5.3.1.Final
    • 5.3.2.Final
    • 5.3.3.Final
    • 5.3.4.Final
    • 5.3.5.Final
    • 5.3.6.Final
    • 5.4.0.Final
    • 5.4.1.Final
    • 5.4.2.Final
    • 5.4.3.Final
    • groupId: org.hibernate.validator
      artifactId: hibernate-validator
      Reference implementation for Bean Validation 2.0
    • 6.0.0.Final
    • 6.0.1.Final
    • 6.0.2.Final
    • 6.0.3.Final
    • 6.0.4.Final
    • 6.0.5.Final
    • 6.0.6.Final
    • 6.0.7.Final
    • 6.0.8.Final
    • 6.0.9.Final
    • 6.0.10.Final
    • 6.0.11.Final
    • 6.0.12.Final
    • 6.0.13.Final
    • 6.0.14.Final
    • 6.0.15.Final
    • 6.0.16.Final
    • 6.0.17.Final
    • 6.0.18.Final
    • 6.0.19.Final
    • 6.0.20.Final
    • 6.0.21.Final
    • 6.0.22.Final
    • 6.1.0.Final
    • 6.1.1.Final
    • 6.1.2.Final
    • 6.1.3.Final
    • 6.1.4.Final
    • 6.1.5.Final
    • 6.1.6.Final
    • 6.1.7.Final
    • 6.2.0.Final
    • Version 7 and later:
      Jakarta Bean Validation 3.0
      jakarta.* packages

    Versions in green have been tested.

  • javax.el-api 3.0.0 (Expression Language 3.0 API)
  • javax.el 2.2.6 (Expression Language 2.2 Implementation)
  • JDK 8
  • Maven 3.8.1

Grouping constraints example Select All Download
  • constraint-groups
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • User.java

    See Also