Hibernate.org Community Documentation

- Prev
- Next

## Chapter 5. Grouping constraints

All validation methods on `Validator` and `ExecutableValidator` discussed in earlier chapters also take a var-arg argument `groups`. So far we have been ignoring this parameter, but it is time to have a closer look.

## 5.1. Requesting groups

Groups allow you to restrict the set of constraints applied during validation. One use case for validation groups are UI wizards where in each step only a specified subset of constraints should get validated. The groups targeted are passed as var-arg parameters to the appropriate validate method.

Let's have a look at an example. The class `Person` in Example 5.1, “Person” has a `@NotNull` constraint on name. Since no group is specified for this annotation the default group `javax.validation.groups.Default` is assumed.

## Note

When more than one group is requested, the order in which the groups are evaluated is not deterministic. If no group is specified the default group `javax.validation.groups.Default` is assumed.

Example 5.1. Person

    package org.hibernate.validator.referenceguide.chapter05;

    public class Person {

        @NotNull

        private String name;

        public Person(String name) {

            this.name = name;

        }

        // getters and setters ...

    }

The class `Driver` in Example 5.2, “Driver” extends `Person` and adds the properties age and hasDrivingLicense. Drivers must be at least 18 years old (`@Min(18)`) and have a driving license (`@AssertTrue`). Both constraints defined on these properties belong to the group `DriverChecks` which is just a simple tagging interface.

## Tip

Using interfaces makes the usage of groups type-safe and allows for easy refactoring. It also means that groups can inherit from each other via class inheritance.

Example 5.2. Driver

    package org.hibernate.validator.referenceguide.chapter05;

    public class Driver extends Person {

        @Min(

                value = 18,

                message = "You have to be 18 to drive a car",

                groups = DriverChecks.class

        )

        public int age;

        @AssertTrue(

                message = "You first have to pass the driving test",

                groups = DriverChecks.class

        )

        public boolean hasDrivingLicense;

        public Driver(String name) {

            super( name );

        }

        public void passedDrivingTest(boolean b) {

            hasDrivingLicense = b;

        }

        public int getAge() {

            return age;

        }

        public void setAge(int age) {

            this.age = age;

        }

    }

    package org.hibernate.validator.referenceguide.chapter05;

    public interface DriverChecks {

    }

Finally the class Car (Example 5.3, “Car”) has some constraints which are part of the default group as well as `@AssertTrue` in the group `CarChecks` on the property passedVehicleInspection which indicates whether a car passed the road worthy tests.

Example 5.3. Car

    package org.hibernate.validator.referenceguide.chapter05;

    public class Car {

        @NotNull

        private String manufacturer;

        @NotNull

        @Size(min = 2, max = 14)

        private String licensePlate;

        @Min(2)

        private int seatCount;

        @AssertTrue(

                message = "The car has to pass the vehicle inspection first",

                groups = CarChecks.class

        )

        private boolean passedVehicleInspection;

        @Valid

        private Driver driver;

        public Car(String manufacturer, String licencePlate, int seatCount) {

            this.manufacturer = manufacturer;

            this.licensePlate = licencePlate;

            this.seatCount = seatCount;

        }

        // getters and setters ...

    }

    package org.hibernate.validator.referenceguide.chapter05;

    public interface CarChecks {

    }

Overall three different groups are used in the example:

- The constraints on Person.name, Car.manufacturer, Car.licensePlate and Car.seatCount all belong to the `Default` group

- The constraints on Driver.age and Driver.hasDrivingLicense belong to `DriverChecks`

- The constraint on Car.passedVehicleInspection belongs to the group `CarChecks`

Example 5.4, “Using validation groups” shows how passing different group combinations to the `Validator#validate()` method results in different validation results.

Example 5.4. Using validation groups

    // create a car and check that everything is ok with it.

    Car car = new Car( "Morris", "DD-AB-123", 2 );

    Set<ConstraintViolation<Car>> constraintViolations = validator.validate( car );

    assertEquals( 0, constraintViolations.size() );

    // but has it passed the vehicle inspection?

    constraintViolations = validator.validate( car, CarChecks.class );

    assertEquals( 1, constraintViolations.size() );

    assertEquals(

            "The car has to pass the vehicle inspection first",

            constraintViolations.iterator().next().getMessage()

    );

    // let's go to the vehicle inspection

    car.setPassedVehicleInspection( true );

    assertEquals( 0, validator.validate( car ).size() );

    // now let's add a driver. He is 18, but has not passed the driving test yet

    Driver john = new Driver( "John Doe" );

    john.setAge( 18 );

    car.setDriver( john );

    constraintViolations = validator.validate( car, DriverChecks.class );

    assertEquals( 1, constraintViolations.size() );

    assertEquals(

            "You first have to pass the driving test",

            constraintViolations.iterator().next().getMessage()

    );

    // ok, John passes the test

    john.passedDrivingTest( true );

    assertEquals( 0, validator.validate( car, DriverChecks.class ).size() );

    // just checking that everything is in order now

    assertEquals(

            0, validator.validate(

            car,

            Default.class,

            CarChecks.class,

            DriverChecks.class

    ).size()

    );

The first `validate()` call in Example 5.4, “Using validation groups” is done using no explicit group. There are no validation errors, even though the property passedVehicleInspection is per default `false`. However, the constraint defined on this property does not belong to the default group.

The next validation using the `CarChecks` group fails until the car passes the vehicle inspection. Adding a driver to the car and validating against `DriverChecks` again yields one constraint violation due to the fact that the driver has not yet passed the driving test. Only after setting passedDrivingTest to `true` the validation against `DriverChecks` passes.

The last validate() call finally shows that all constraints are passing by validating against all defined groups.

## 5.2. Defining group sequences

By default, constraints are evaluated in no particular order, regardless of which groups they belong to. In some situations, however, it is useful to control the order constraints are evaluated.

In the example from Example 5.4, “Using validation groups” it could for instance be required that first all default car constraints are passing before checking the road worthiness of the car. Finally, before driving away, the actual driver constraints should be checked.

In order to implement such a validation order you just need to define an interface and annotate it with `@GroupSequence`, defining the order in which the groups have to be validated (see Example 5.5, “Defining a group sequence”). If at least one constraint fails in a sequenced group none of the constraints of the following groups in the sequence get validated.

Example 5.5. Defining a group sequence

    package org.hibernate.validator.referenceguide.chapter05;

    @GroupSequence({ Default.class, CarChecks.class, DriverChecks.class })

    public interface OrderedChecks {

    }

## Warning

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. If a group containing such a circularity is evaluated, a `GroupDefinitionException` is raised.

You then can use the new sequence as shown in in Example 5.6, “Using a group sequence”.

Example 5.6. Using a group sequence

    Car car = new Car( "Morris", "DD-AB-123", 2 );

    car.setPassedVehicleInspection( true );

    Driver john = new Driver( "John Doe" );

    john.setAge( 18 );

    john.passedDrivingTest( true );

    car.setDriver( john );

    assertEquals( 0, validator.validate( car, OrderedChecks.class ).size() );

## 5.3. Redefining the default group sequence

### 5.3.1. `@GroupSequence`

Besides defining group sequences, the `@GroupSequence` annotation also allows to redefine the default group for a given class. To do so, just add the `@GroupSequence` annotation to the class and specify the sequence of groups which substitute `Default` for this class within the annotation.

Example 5.7, “Class RentalCar with redefined default group” introduces a new class `RentalCar` with a redefined default group.

Example 5.7. Class `RentalCar` with redefined default group

    package org.hibernate.validator.referenceguide.chapter05;

    @GroupSequence({ RentalChecks.class, CarChecks.class, RentalCar.class })

    public class RentalCar extends Car {

        @AssertFalse(message = "The car is currently rented out", groups = RentalChecks.class)

        private boolean rented;

        public RentalCar(String manufacturer, String licencePlate, int seatCount) {

            super( manufacturer, licencePlate, seatCount );

        }

        public boolean isRented() {

            return rented;

        }

        public void setRented(boolean rented) {

            this.rented = rented;

        }

    }

    package org.hibernate.validator.referenceguide.chapter05;

    public interface RentalChecks {

    }

With this definition you can evaluate the constraints belonging to `RentalChecks`, `CarChecks` and `RentalCar` by just requesting the `Default` group as seen in Example 5.8, “Validating an object with redefined default group”.

Example 5.8. Validating an object with redefined default group

    RentalCar rentalCar = new RentalCar( "Morris", "DD-AB-123", 2 );

    rentalCar.setPassedVehicleInspection( true );

    rentalCar.setRented( true );

    Set<ConstraintViolation<RentalCar>> constraintViolations = validator.validate( rentalCar );

    assertEquals( 1, constraintViolations.size() );

    assertEquals(

            "Wrong message",

            "The car is currently rented out",

            constraintViolations.iterator().next().getMessage()

    );

    rentalCar.setRented( false );

    constraintViolations = validator.validate( rentalCar );

    assertEquals( 0, constraintViolations.size() );

## Note

Since there must no cyclic dependency in the group and group sequence definitions one cannot just add `Default` to the sequence redefining `Default` for a class. Instead the class itself has to be added!

The `Default` group sequence overriding is local to the class it is defined on and is not propagated to associated objects. For the example this means that adding `DriverChecks` to the default group sequence of `RentalCar` would not have any effects. Only the group `Default` will be propagated to the driver association.

Note that you can control the propagated group(s) by declaring a group conversion rule (see Section 5.4, “Group conversion”).

### 5.3.2. `@GroupSequenceProvider`

In addition to statically redefining default group sequences via `@GroupSequence`, Hibernate Validator also provides an SPI for the dynamic redefinition of default group sequences depending on the object state.

For that purpose you need to implement the interface `DefaultGroupSequenceProvider` and register this implementation with the target class via the `@GroupSequenceProvider` annotation. In the rental car scenario you could for instance dynamically add the `CarChecks` as seen in Example 5.9, “Implementing and using a default group sequence provider”.

Example 5.9. Implementing and using a default group sequence provider

    package org.hibernate.validator.referenceguide.chapter05.groupsequenceprovider;

    public class RentalCarGroupSequenceProvider

            implements DefaultGroupSequenceProvider<RentalCar> {

        @Override

        public List<Class<?>> getValidationGroups(RentalCar car) {

            List<Class<?>> defaultGroupSequence = new ArrayList<Class<?>>();

            defaultGroupSequence.add( RentalCar.class );

            if ( car != null && !car.isRented() ) {

                defaultGroupSequence.add( CarChecks.class );

            }

            return defaultGroupSequence;

        }

    }

    package org.hibernate.validator.referenceguide.chapter05.groupsequenceprovider;

    @GroupSequenceProvider(RentalCarGroupSequenceProvider.class)

    public class RentalCar extends Car {

        @AssertFalse(message = "The car is currently rented out", groups = RentalChecks.class)

        private boolean rented;

        public RentalCar(String manufacturer, String licencePlate, int seatCount) {

            super( manufacturer, licencePlate, seatCount );

        }

        public boolean isRented() {

            return rented;

        }

        public void setRented(boolean rented) {

            this.rented = rented;

        }

    }

## 5.4. Group conversion

What if you wanted to validate the car related checks together with the driver checks? Of course you could pass the required groups to the validate call explicitly, but what if you wanted to make these validations occur as part of the `Default` group validation? Here `@ConvertGroup` comes into play which allows you during cascaded validation to use a different group than the originally requested one.

Let's have a look at Example 5.10, “@ConvertGroup usage”. Here `@GroupSequence({ CarChecks.class, Car.class })` is used to combine the car related constraints under the `Default` group (see Section 5.3, “Redefining the default group sequence”). There is also a `@ConvertGroup(from = Default.class, to = DriverChecks.class)` which ensures the `Default` group gets converted to the `DriverChecks` group during cascaded validation of the driver association.

Example 5.10. `@ConvertGroup` usage

    package org.hibernate.validator.referenceguide.chapter05.groupconversion;

    public class Driver {

        @NotNull

        private String name;

        @Min(

                value = 18,

                message = "You have to be 18 to drive a car",

                groups = DriverChecks.class

        )

        public int age;

        @AssertTrue(

                message = "You first have to pass the driving test",

                groups = DriverChecks.class

        )

        public boolean hasDrivingLicense;

        public Driver(String name) {

            this.name = name;

        }

        public void passedDrivingTest(boolean b) {

            hasDrivingLicense = b;

        }

        // getters and setters ...

    }

    package org.hibernate.validator.referenceguide.chapter05.groupconversion;

    @GroupSequence({ CarChecks.class, Car.class })

    public class Car {

        @NotNull

        private String manufacturer;

        @NotNull

        @Size(min = 2, max = 14)

        private String licensePlate;

        @Min(2)

        private int seatCount;

        @AssertTrue(

                message = "The car has to pass the vehicle inspection first",

                groups = CarChecks.class

        )

        private boolean passedVehicleInspection;

        @Valid

        @ConvertGroup(from = Default.class, to = DriverChecks.class)

        private Driver driver;

        public Car(String manufacturer, String licencePlate, int seatCount) {

            this.manufacturer = manufacturer;

            this.licensePlate = licencePlate;

            this.seatCount = seatCount;

        }

        // getters and setters ...

    }

As a result the validation in Example 5.11, “Test case for @ConvertGroup” succeeds, even though the constraint on hasDrivingLicense belongs to the `DriverChecks` group and only the `Default` group is requested in the `validate()` call.

Example 5.11. Test case for @ConvertGroup

    // create a car and validate. The Driver is still null and does not get validated

    Car car = new Car( "VW", "USD-123", 4 );

    car.setPassedVehicleInspection( true );

    Set<ConstraintViolation<Car>> constraintViolations = validator.validate( car );

    assertEquals( 0, constraintViolations.size() );

    // create a driver who has not passed the driving test

    Driver john = new Driver( "John Doe" );

    john.setAge( 18 );

    // now let's add a driver to the car

    car.setDriver( john );

    constraintViolations = validator.validate( car );

    assertEquals( 1, constraintViolations.size() );

    assertEquals(

            "The driver constraint should also be validated as part of the default group",

            constraintViolations.iterator().next().getMessage(),

            "You first have to pass the driving test"

    );

You can define group conversions wherever `@Valid` can be used, namely associations as well as method and constructor parameters and return values. Multiple conversions can be specified using `@ConvertGroup.List`.

However, the following restrictions apply:

- `@ConvertGroup` must only be used in combination with `@Valid`. If used without, a `ConstraintDeclarationException` is thrown.

- It is not legal to have multiple conversion rules on the same element with the same `from` value. In this case, a `ConstraintDeclarationException` is raised.

- The `from` attribute must not refer to a group sequence. A `ConstraintDeclarationException` is raised in this situation.

## Note

Rules are not executed recursively. The first matching conversion rule is used and subsequent rules are ignored. For example if a set of `@ConvertGroup` declarations chains group A to B and B to C, the group A will be converted to B and not to C.

Copyright © 2009 - 2013 Red Hat, Inc. & Gunnar Morling

- PrevChapter 4. Interpolating constraint error messages
- Top of page
- Front page
- NextChapter 6. Creating custom constraints