JavaBean Validation - Using @ConvertGroup to use a different group with @Valid annotation (during cascaded validation)

[Updated: Aug 17, 2017, Created: Aug 17, 2017]

According to Bean validation specification, when performing cascading validation (via @Valid annotation), it is possible to use a different group than the one originally specified using the group conversion feature.

Group conversions are declared by using the @ConvertGroup annotation. Following is this annotation's definition snippet:

package javax.validation.groups;
 ......

@Target({ TYPE, METHOD, FIELD, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
public @interface ConvertGroup {
	Class<?> from();
	Class<?> to();

	@Target({ TYPE, METHOD, FIELD, CONSTRUCTOR, PARAMETER })
	@Retention(RUNTIME)
	@Documented
	public @interface List {
		ConvertGroup[] value();
	}
}
  • @ConvertGroup can be used everywhere @valid can be used.
  • The conversion is declared by specifying 'to' and 'from' elements.
  • ConvertGroup.List can be used to defined several group conversions.

Example

public class DriverLicense {
  @NotNull
  @Valid
  @ConvertGroup(from = LicenceNumberCheck.class,
          to = DriverPhysicalRequirement.class)
  private Driver driver;
  @Digits(integer = 7, fraction = 0, groups = LicenceNumberCheck.class)
  private int number;

  public DriverLicense(Driver driver, int number) {
      this.driver = driver;
      this.number = number;
  }
}
public class Driver {
  @NotNull
  private String fullName;
  @Min(value = 100, groups = DriverPhysicalRequirement.class)
  private int height;
  @Past(groups = DriverPhysicalRequirement.class)
  @NotNull
  private Date dateOfBirth;

  public Driver(String fullName, int height, Date dateOfBirth) {
      this.dateOfBirth = dateOfBirth;
      this.fullName = fullName;
      this.height = height;
  }
}
public interface LicenceNumberCheck {
}
public interface DriverPhysicalRequirement {
}

The main class

public class ConvertGroupExample {

  public static void main(String[] args) throws ParseException {
      Driver driver = new Driver(null, 60,
              new Date(System.currentTimeMillis() + 100000));
      DriverLicense dl = new DriverLicense(driver, 3454343);

      Validator validator = createValidator();
      Set<ConstraintViolation<DriverLicense>> violations =
              validator.validate(dl, LicenceNumberCheck.class, Default.class);
      if (violations.size() == 0) {
          System.out.println("No violations.");
      } else {
          System.out.printf("%s violations:%n", violations.size());
          violations.stream()
                    .forEach(ConvertGroupExample::printError);
      }
  }

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

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

Output

3 violations:
driver.dateOfBirth must be in the past
driver.fullName may not be null
driver.height must be greater than or equal to 100

In above example, if we comment out @ConvertGroup then no validation will be performed in the Driver class.

Using default group:

public class ConvertGroupDefaultExample {

  public static void main(String[] args) throws ParseException {
      Driver driver = new Driver(null, 60,
              new Date(System.currentTimeMillis() + 100000));
      DriverLicense dl = new DriverLicense(driver, 3454343);

      Validator validator = createValidator();
      Set<ConstraintViolation<DriverLicense>> violations = validator.validate(dl);
      if (violations.size() == 0) {
          System.out.println("No violations.");
      } else {
          System.out.printf("%s violations:%n", violations.size());
          violations.stream()
                    .forEach(ConvertGroupDefaultExample::printError);
      }
  }

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

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

Output

1 violations:
driver.fullName may not be null

Example Project

Dependencies and Technologies Used :

  • hibernate-validator 5.2.4.Final: Hibernate's Bean Validation (JSR-303) reference implementation.
  • javax.el-api 2.2.4 Expression Language API 2.2
  • javax.el 2.2.4 Expression Language 2.2 Implementation
  • JDK 1.8
  • Maven 3.3.9

@ConvertGroup Example Select All Download
  • convert-group-example
    • src
      • main
        • java
          • com
            • logicbig
              • example

See Also