When using a @GroupSequenceProvider, how do you get all ConstraintViolations reported at once?


#1

When using a @GroupSequenceProvider, how do you get all ConstraintViolations reported at once?

Consider this class:

@Data
@AllArgsConstructor
@GroupSequenceProvider(CarGroupSequenceProvider.class)
public class Car {

  @NotBlank
  private String make;

  @NotBlank
  private String model;

  private boolean registered;

  @NotBlank(groups = RegistrationValidationGroup.class)
  private String licensePlate;

}

With this GroupSequenceProvider:

public class CarGroupSequenceProvider implements DefaultGroupSequenceProvider<Car> {

  @Override
  public List<Class<?>> getValidationGroups(Car car) {

    System.out.println("CarGroupSequenceProvider::getValidationGroups called with car=" + car);

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

    groupSequence.add(Car.class);

    if (car != null) {

      if (car.isRegistered()) {
        groupSequence.add(RegistrationValidationGroup.class);
      }

    }

    System.out.println("Returning groupSequence:" + groupSequence);

    return groupSequence;

  }

}

When you run this test:

public class CarValidationTest {

  private static Validator validator;

  @BeforeClass
  public static void setUpValidator() {
    ValidatorFactory factory = Validation.buildDefaultValidatorFactory().getValidator();
  }

  @Test
  public void testAllRegistrationRequiredFields() {

    Car car = new Car(null, null, true, null);

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

    assertEquals(3, constraintViolations.size());

  }

}

You’ll see this output:

CarGroupSequenceProvider::getValidationGroups called with car=null
Returning groupSequence:[class Car]
CarGroupSequenceProvider::getValidationGroups called with car=Car(make=null, model=null, registered=true, licensePlate=null)
Returning groupSequence:[class Car, interface RegistrationValidationGroup]
CarGroupSequenceProvider::getValidationGroups called with car=Car(make=null, model=null, registered=true, licensePlate=null)
Returning groupSequence:[class Car, interface RegistrationValidationGroup]

And the test fails because there are only 2 ConstraingViolations provided (one for a blank make and one for a blank model), but I’m hoping to get a third as well, because RegistrationValidationGroup was included and will trigger a ConstraintViolation, as shown in this test, which passes:

public class CarValidationTest {

  private static Validator validator;

  @BeforeClass
  public static void setUpValidator() {
    ValidatorFactory factory = Validation.buildDefaultValidatorFactory().getValidator();
  }

  @Test
  public void testRegistrationRequiredFields() {

    Car car = new Car("Tesla", "Model S", true, null);

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

    assertEquals(1, constraintViolations.size());

  }

}

#2

You can’t. That’s the whole point of using groups: the validation routine validates the current group and only passes to the next one if there are no violations for the current group.

Keep in mind that the main reason why groups were instated is to have a group with quick checks then a group with potentially resource-heavy checks.


#3

@gsmet Thanks for getting back to me.

I wasn’t aware about the original intent of the group functionally.

Is there another way to dynamically enable/disable validations based on runtime state other then to custom code a class validator so that we can get all ConstratintViolations reported at once?

Custom class validators don’t offer the same declarative nature of annotation based validations on the fields, so I prefer to avoid them.


#4

You can require several groups by passing them to the validate() call or by using group inheritance.

It’s just group sequence that is designed to execute the groups in order and fail as soon as one group fails.