One of the most important pieces of any data-using application is data validation. It is imperative that one validates data before it is inserted into a database in order to maintain integrity. There are a number of reasons to validate, the most important being security purposes, data consistency, and proper formatting. Many web applications use validation in the presentation layer via JavaScript for validation of form data and also in the persistence layer. However, sometimes JavaScript can become problematic in that the code can become unwieldy and there is also no guarantee that it will execute. It is oftentimes found to be a good idea to perform validation within the domain model, although this can cause code clutter.

In this chapter, we will take a look at the Bean Validation API, which is used to apply validation to a JavaBean. In the context of Jakarta EE, since JPA entity classes are Plain Old Java Objects (POJOs), this allows developers to make use of Bean Validation on entity classes and entity class attributes. A Java controller class may have validation logic to ensure that only specific data passes through to the database. The Bean Validation API is another means of performing data validation in either the domain model or presentation layer via metadata, using an annotation-based approach. To validate with this API, one simply applies validation constraint annotations on the entity class attribute(s), as needed, and the constraint validators will automatically enforce the validation. Bean Validation was first introduced into Jakarta EE platform with Java EE 6, and it had been given a face-lift in Java EE 8, introducing a number of new features for the Bean Validation 2.0 release. Although this chapter will focus on the use of Bean Validation with Jakarta EE, it can be used in JavaBeans across all different flavors of Java, be it Java FX, previous Java EE releases, or Java SE. Bean validation contains an API that can be used to manually invoke validation, but in most cases the validation occurs automatically because of the integration that has been made across the various Jakarta EE specifications.

Bean Validation annotation constraints can be applied on types, fields, methods, constructors, parameters, container elements, and other container annotations. Validation is applied not only to the object level, but it can also be inherited from superclasses. Entire object graphs can be validated, meaning that if a class declares a field which has the type of a separate class containing validation, cascading validation can occur.

This chapter will demonstrate examples of each validation type, explaining the strongholds for each of the different methodologies. In the end, you will have a good understanding of how the Bean Validation API works, and you should be able to apply Bean Validation strategies to your applications.

FormalPara Note

Bean Validation allows one to declare constraints via XML, rather than annotations. For the purposes of brevity, this chapter will not cover this feature. For more information, please see the Bean Validation 2.0 specification (https://jakarta.ee/specifications/bean-validation/2.0/bean-validation_2.0.html).

10.1 Validating Fields with Built-in Constraints

10.1.1 Problem

Imagine that you create a Chapter entity class, which will be used to store the contents regarding a book chapter. In doing so, you wish to apply validation to specified fields of the entity class such that only compliant data is allowed to be inserted or updated in the database. In this case, suppose that there are a number of fields that must contain values, and you also want to be certain that Strings of text are within the size limits of the underlying database field.

10.1.2 Solution #1

Apply the pertinent Bean Validation constraints to field(s) that you wish to validate. In this example, the standard @NotNull and @Size constraint annotations are placed on specific fields of the Chapter entity. Namely, the id attribute is marked as @NotNull so that it must contain a value, and the title attribute is marked to have a maximum size of 150 characters:

@Entity @Table(name = "CHAPTER") public class Chapter implements Serializable {     private static final long serialVersionUID = 1L;     @Id     @Basic(optional = false)     @NotNull     @Column(name = "ID")     private BigDecimal id;     @Column(name = "CHAPTER_NUMBER")     private BigDecimal chapterNumber;     @Size(max = 150)     @Column(name = "TITLE")     private String title;     @Lob     @Column(name = "DESCRIPTION")     private String description; . . .

10.1.3 Solution #2

Apply the pertinent Bean Validation constraint annotations to the getter methods (accessor methods) of the field(s) you wish to validate. In this case, the following example shows the getId() and getTitle() methods . Each of these accessor methods is annotated accordingly:

@NotNull private BigDecimal getId(){     return this.id; } . . . @Size(max=150) private String getTitle(){     return this.title; }

10.1.4 How It Works

The Bean Validation API provides a number of built-in constraint definitions that are ready to use. These standard constraints span the array of common use cases for data validation. Table 10-1 lists each of the standard validation constraint annotations along with a description of each.

Table 10-1 Standard Built-In Constraints

To apply validation to a field, simply specify the built-in or custom Bean Validation annotation to the field declaration, along with the appropriate constraint attribute(s). You also have the option of annotating a field’s corresponding getter method, rather than the field declaration itself. Any single field may have more than one annotation constraint applied to it. You are welcome to combine constraints to suit the requirement. If an annotated class extends another class that contains Bean Validation constraints, then those constraints are applied to all annotated fields, whether the field belongs to the extended class or the class that extends.

Attributes are used to associate metadata with the annotations for specifying information such as the error message that is to be displayed should the validation fail or the number of characters to be validated. Table 10-2 lists the common constraint annotation attributes that you’ll find across each of the different constraints. These are all considered reserved names.

Table 10-2 Common Constraint Annotation Attributes

Most of the constraint attributes are optional. However, in some cases, an attribute should be specified. For instance, when applying the @Size constraint, if the max attribute is not specified, the default is 2147483647. Therefore, given that someone will likely never enter a value that large, one should specify a maximum size using the max attribute. The groups attribute is used to specify if a particular annotation constraint is part of a processing group. A validation group defines a subset of constraints, and a group can simply be an empty interface. Groups are used to control the order of evaluation for constraints or to perform partial state validation for a JavaBean. To learn more about applying groups, please refer to Recipe 10-8. The payload attribute is used to assign a payload to a validation annotation. Payloads are typically used by bean validation clients to associate some kind of metadata information. A payload is usually defined as a class that implements the Payload interface. Payloads can be seen in more detail with Recipe 10-9.

10.2 Writing Custom Constraint Validators

10.2.1 Problem

Your application requires a specific validation that is not provided among the built-in Bean Validation constraints. For example, you wish to validate that a book title includes the word “Java” in the title.

10.2.2 Solution

Implement a custom constraint validator for the application. A custom constraint can be created by developing a constraint annotation along with a validator implementation class and a default error message. The example that follows demonstrates a custom constraint that is used to compare whether a String contains the text “Java”. The annotation class for such a constraint may resemble the following:

import java.lang.annotation.Documented; import static java.lang.annotation.ElementType.ANNOTATION_TYPE; import static java.lang.annotation.ElementType.FIELD; import java.lang.annotation.Retention; import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Target; import javax.validation.Payload; @Target({ FIELD, ANNOTATION_TYPE}) @Retention(RUNTIME) @Documented public @interface JavaBookTitle {     String message() default "{org.jakartaeerecipes.annotation." +             "message}";     Class<?>[] groups() default { };     Class<? extends Payload>[] payload() default { }; }

The implementation for the validator should look like that of the BookTitleValidator class:

import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; public class BookTitleValidator implements ConstraintValidator<JavaBookTitle, String> {     @Override     public void initialize(JavaBookTitle constraintAnnotation) {     }     @Override     public boolean isValid(String title, ConstraintValidatorContext cvc) {         if(title.toUpperCase().contains("JAVA")){             return true;         } else {             return false;         }     } }

Now that the constraint annotation has been created, it can be applied to a field as follows:

@JavaBookTitle(message = "Book Title Should Contain The Word Java") @Column(name = "TITLE") protected String title ;

10.2.3 How It Works

Creating a custom validation constraint annotation is quite easy, although the implementation may look a bit daunting at first glance. A validation constraint consists of the following pieces:

  • Constraint annotation

  • Validator implementation class

  • Default error message

The constraint annotation is created just like any standard Java annotation. The annotation declaration is a standard Java interface. The interface must be annotated with @Target, passing a list in curly brackets, which specifies the types that the annotation can be applied to. The @Retention annotation can also be specified on the declaration, passing a value to specify how long the annotation will be retained. Valid values include SOURCE, CLASS, and RUNTIME. An annotation declaration may also include the @Documented annotation , which indicates whether an annotation declaration will be documented by JavaDoc by default.

Annotation declaration interfaces can contain elements to associate metadata to a validation constraint. A constraint annotation must contain three elements: a message element of type String, a groups() method , and a payload() method . Each of the elements can be declared within the annotation constraint with a default value, as seen in the example. The message element is used to create a default error message for the validator. The message may include String interpolation, and it may also be loaded from a resource bundle to take advantage of features such as internationalization. The groups() method is used to specify any processing groups to which the constraint will belong. The Default group is declared if no group is specified and the array is empty. The payload() method is typically used to associate metadata with a given validation constraint.

A validationAppliesTo element can be used to specify which targets the constraint associates against. Lastly, one may choose to declare a custom element to assist in the validation of values.

The next piece of required code is the constraint implementation class. This class should implement CustomValidator<AnnotationType, Type>. In doing so, this class must override the initialize() and isValid() methods . If there is any data that needs to be initialized prior to validation, then it should be done within the initialize method. The isValid() method should accept the data to be validated, along with ConstraintValidatorContext as arguments. The implementation of the method should validate the data and return a Boolean to indicate whether or not the data complies with the constraint.

Once these pieces of code are in place, the annotation can be specified on the targets for validation. The annotation should specify additional elements to associate metadata, if needed. In the example, the annotation specifies the message element, which allows a default error message to be declared.

10.3 Validating at the Class Level

10.3.1 Problem

You wish to validate some or all of the fields within an object to ensure that those fields of the object contain valid data as a whole. For instance, you must validate that a field declared as numChapters contains the same number of chapters as those in a field of type List<Chapter>.

10.3.2 Solution

Specify class-level constraints to perform the validation. In the example for this recipe, the Book entity contains a number of fields that must validate against one another in order to constitute a valid object:

@ValidNumChapters public class Book implements Serializable {     private static final long serialVersionUID = 1L;     // @Max(value=?)  @Min(value=?)//if you know range of your decimal fields consider using these annotations to enforce field validation     @Id     @Basic(optional = false)     @NotNull     @Column(name = "ID")     private BigDecimal id;     @Size(max = 150)     @Column(name = "TITLE")     protected String title;     @Size(max = 500)     @Column(name = "IMAGE")     private String image;     @Column(name = "NUM_CHAPTERS")     private int numChapters;     @Column(name = "NUM_PAGES")     private int numPages;     @Lob     @Column(name = "DESCRIPTION")     private String description;     @Column(name = "PUBLISH_DATE")     private LocalDate publishDate;     @ManyToMany(mappedBy="books")     private Set<BookAuthor> authors;     @OneToMany(mappedBy="book", cascade=CascadeType.ALL)     private List<Chapter> chapters = null;

The @ValidateNumChapters annotation is used to validate that the numChapters value is greater than or equal to the chapters List. Following this logic, a Book may be in progress and more Chapter objects can be added to the list as completed, but there cannot be more Chapter objects than the numChapters value. As covered in Recipe 10-2, in order to create the @ValidateNumChapters annotation, there must be an annotation declaration class and a constraint implementation class. The following class is used to declare the annotation :

import java.lang.annotation.Documented; import static java.lang.annotation.ElementType.ANNOTATION_TYPE; import static java.lang.annotation.ElementType.TYPE; import java.lang.annotation.Retention; import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Target; import javax.validation.Payload; @Target({ TYPE, ANNOTATION_TYPE}) @Retention(RUNTIME) @Documented public @interface ValidNumChapters {     String message() default "{org.jakartaeerecipes.annotation." +             "message}";     Class<?>[] groups() default { };     Class<? extends Payload>[] payload() default { }; }

The annotation class contains the default error message as well as the declaration for the annotation itself. The following class is the constraint validation implementation for validating the number of chapters in the book :

public class NumChaptersValidator implements ConstraintValidator<ValidNumChapters, Book> {     @Override     public void initialize(ValidNumChapters constraintAnnotation) {     }     @Override     public boolean isValid(Book book, ConstraintValidatorContext cvc) {         if (book == null){             return true;         }         return book.getChapters().size() <= book.getNumChapters();     } }

Once these classes have been created, the annotation can be placed onto the class(es) accordingly .

10.3.3 How It Works

The class-level validation constraint is one that can be quite powerful, as it can pose a validation on one or more fields of the class. Applying a validation constraint to the class level means that one or more of the fields must adhere to the validation constraint. In the cases where a built-in validation constraint is applied at the class level, all fields of the class must adhere to the constraint. For instance, if @NotNull were applied, then each field within the class must be populated for each instance. On the other hand, applying a constraint such as @Size at the class level would not work if the class contained fields that were of a type other than String. More often, class-level constraints are custom created and apply only to a specified subset of the class fields.

In the recipe example, the @ValidNumChapters constraint is placed at the class level, which means that this particular constraint has access to each of the fields within the class. However, the constraint implementation only actually validates the numChapters and chapters fields to determine if the number of Chapter objects within the array is less than or equal to the numChapters value . Recipe 10-2 covers the declaration of an annotation in detail, so I won’t cover that here.

The important piece of the puzzle for this recipe is to look closely at the annotation implementation within the NumChaptersValidator class. To create this implementation, the class must implement ConstraintValidator<A extends Annotation, T>. The ConstraintValidator interface utilizes generics to specify the constraint along with an object which will be validated. In the signature of the interface, A must be the name of an annotation declaration class, so in this case ValidNumChapters. T is any given object that must resolve to a non-parameterized type, or generic parameters of T must be unbounded wildcard types. In the example, the Book entity class is specified as T.

The interface enforces the implementation of the initialize(A constraintAnnotation) and isValid(T value, ConstraintValidatorContext context) methods . Many times, the initialize method can be empty, but if needed, it should contain code to initialize the validator in preparation for call to isValid. The isValid method contains the actual validation logic. The Book object that is passed into the method is first checked to ensure that it is not null. If it is null, then true is returned; otherwise, the number of Chapter objects in the chapters List is compared against the numChapters value to return a Boolean result.

Class-level validation can be very powerful, as it allows validation of class fields in a custom manner. It is also very easy to implement, making it even more powerful when validating complex objects such as those that contain Lists of other objects.

10.4 Validating Parameters

10.4.1 Problem

You want to specify some preconditions on a method such that the parameters adhere to a specified constraint.

10.4.2 Solution

Apply validation constraint annotations to method parameter(s) such that the parameter(s) will be validated by either built-in or custom constraints. This will enforce constraint logic at the time of a method call such that only arguments that meet the specified constraints will be acceptable as parameters to a given method. In the following example, a method that accepts a parameter is demonstrated including a validation constraint:

public void submitEmailAddress(@Email String emailAddress){     System.out.println("Do something with the address: " + emailAddress); }

In this particular example, a single parameter is being validated. However, it is possible to include more than one parameter containing a validation constraint. It is also possible to include a cross-parameter constraint at the method level, which can be used to apply validation across all of the method parameters.

10.4.3 How It Works

Bean Validation makes it possible to include validation constraints on non-static method parameters for the purposes of applying preconditions that will ensure invalid data cannot be passed into the method. Either built-in or custom validation constraints can be applied to method parameters. If an invalid value is passed into a method that contains a parameter constraint, a validation error will be thrown, and the method will not be executed.

In the example, the submitEmailAddress method accepts a single parameter, emailAddress. If the emailAddress parameter does not adhere to a valid email format, as specified via the @Email validation constraint, the method call will fail. It is also possible to validate more than a single method parameter by applying a constraint validation on the method itself. Doing so is much the same as applying a class-level constraint (Recipe 10-3) in that the constraint can be either built-in or custom. Built-in constraints applied at the method level, such as @NotNull, would be applied to each of the parameters of the method. Custom constraints can be created in the same manner as previously shown in Recipe 10-3 whereby one or more parameters can be validated. Constraints that are placed at the method level must be configured within the ConstraintValidator implementation using the @SupportedValidationTarget annotation to indicate that the constraint is to be placed on the method level. This is because return-type constraints are also placed at the method level, so the @SupportedValidationTarget helps to distinguish which validation type shall occur. The following code example demonstrates how to write a constraint validation implementation targeted for use at the method level:

@SupportedValidationTarget(value = ValidationTarget.PARAMETERS) public class ValidEmployeeEmailValidator implements ConstraintValidator<ValidEmployeeEmail, Object[]> {     @Override     public void initialize(final ValidEmployeeEmail constraintAnnotation) {         // no-op     }     @Override     public boolean isValid(final Object[] parameters, final ConstraintValidatorContext context) {         // Ensure employee email is from our organization         return parameters == null || parameters[0].toString().contains("@acme.com");     } }

10.5 Constructor Validation

10.5.1 Problem

You wish to validate the instantiation of a class through the validation of constructor parameters.

10.5.2 Solution

Apply validation constraint annotations to the individual constructor parameters or at the constructor level itself to perform validation. In the following example, a constructor is annotated with @NotNull at the constructor level. Therefore, the @NotNull validation constraint is applied across each of the constructor parameters:

@NotNull public ConstructorValidationController(String parameterOne,                                        String parameterTwo){     this.p1 = parameterOne;     this.p2 = parameterTwo; }

10.5.3 How It Works

In some cases, it makes sense to validate parameters that are passed into a class at the time of instantiation. Bean Validation makes this easy by allowing one to apply constraint annotations to parameters of a class constructor or to the constructor itself. When an annotation is placed on the parameters of a class constructor, the class cannot be instantiated if the validations fail. Similarly, if a constraint annotation is placed on the constructor declaration itself, then it will be applied across all of the constructor parameters. Such a constraint that is applied at the constructor level is called a cross-parameter constraint.

In the example, a cross-parameter @NotNull annotation is applied to the constructor of a class. Each of the parameters of the constructor must contain a value; otherwise, the validation will fail, and the class will not be instantiated. As mentioned previously, if a custom annotation were placed on the constructor, it could validate one or more of the parameters, just like a method-level constraint .

Note

Take special care to ensure that unintended behavior does not occur as a result of subtype constructor constraints. It is important to keep the object hierarchy in mind when applying validation constraints on a class or class constructor.

10.6 Validating Return Values

10.6.1 Problem

You wish to validate the return value of a method, such that the returning value must adhere to a constraint. If the return value does not adhere, then a validation exception shall be thrown.

10.6.2 Solution

Place a validation constraint on the return type of a method signature to ensure that the result will conform to the validation constraint. In the following example, the return type of the method is validated by the annotation which is placed at the method level. In this case, the returned value must be in an email address format:

@Email public String getEmailAddress(){     return emailAddress; }

10.6.3 How It Works

Validation constraint annotations that are placed at the method level can be targeted toward return value validation. In doing so, a method must return a valid value per the constraint; otherwise, a bean validation exception is thrown. In order to ensure that the constraint being placed at the method level is targeted toward a return type validation, the validator implementation must contain the @SupportedValidationTarget annotation, which specifies whether the validator applies to parameters or to the method itself. If a validation constraint implementation does not include this specification, there is no way for the Bean Validation API to determine where the validation should be applied. In this case, the implementation would specify the following:

@SupportedValidationTarget(value = ValidationTarget.ANNOTATED_ELEMENT)

As with many of the other validation types, return-type validation can utilize both standard and custom constraints.

10.7 Defining a Dynamic Validation Error Message

10.7.1 Problem

You wish to supply a dynamic error message containing information that is pertinent to the validated value for a constraint.

10.7.2 Solution

Utilize String interpolation within the Bean Validation message attribute. String interpolation allows one to place message parameters and message expressions into a message String, thereby creating a dynamic String-based message. In the following example, the actual length of the String value will be substituted into the message to provide more feedback:

@Size(max = 150, message="The title cannot exceed {max} characters, current title is $'{validatedValue}'") @Column(name = "TITLE") protected String title;

10.7.3 How It Works

Providing a clear error message for the user can make or break the success of an application. Utilizing String interpolation within an error message can help one to provide a specific message to the user to help indicate the cause of the validation failure. In much the same way that substitution variables work, an error message can contain zero or more variables that can be substituted.

Note

String interpolation requires expression language libraries to be available within your project. If the expression language API and an implementation library are not added to the project, errors will be thrown at runtime. The following Maven dependencies can be added to fulfil this requirement:

<dependency>     <groupId>javax.el</groupId>     <artifactId>javax.el-api</artifactId>     <version>3.0.0</version> </dependency> <dependency>     <groupId>org.glassfish.web</groupId>     <artifactId>javax.el</artifactId>     <version>2.2.6</version> </dependency>

To utilize String interpolation, curly braces can be used to surround the variable that will be substituted with dynamic values. Constraint attributes can be interpolated by placing the attribute inside of curly braces. In the example, the max attribute will be substituted for the {max} String interpolation. The validated value or custom expression variables can be specified to substitute values within a message by utilizing the EL notation, thereby enclosing within curly braces and proceeded with a $. In the example, the $'{validatedValue}' variable is one such example. When the error message is produced, $'{validatedValue}' is replaced with the current validated value.

Message interpolation occurs in phases, outlined in the following order:

  1. 1)

    Resolve any message parameters using them as a key for a resource bundle named ValidationMessages.properties.

  2. 2)

    Resolve any message parameters using them as a key for a resource bundle that contains the standard error messages for built-in constraints.

  3. 3)

    Utilize a value constraint annotation member to substitute the message parameter. As such, the message parameters will simply be replaced by the value constraint annotation member of the same name.

  4. 4)

    Resolve any message parameters using evaluations as expressions of the Unified Expression Language. This allows us to formulate error messages based upon conditional logic and enables us to achieve advanced formatting.

The characters {,} and $ are special characters for message descriptors. Therefore, if one wishes to utilize one of these characters within a validation error message, it must be escaped by proceeding it with a \. Therefore, to escape a $, one would use \$.

It is possible to define a custom message interpolation algorithm, if needed, by plugging in a custom MessageInterplator implementation. To develop a custom interpolator, implement the javax.validation.MessageInterpolator interface. The interpolator must be thread-safe, and it is recommended to delegate the final implementation to the default interpolator. The default interpolator is available by calling upon Configuration.getDefaultMessageInterpolator(). For more information, please refer to the Bean Validation specification.

10.8 Manually Invoking the Validator Engine

10.8.1 Problem

You wish to call upon the Bean Validation validator engine programmatically, rather than relying upon automatic invocation.

10.8.2 Solution

Utilize the Validator API to perform validation. The Validator API allows one to create an executable validator from a number of different validation types, those being parameter, return, class, and so on. The following example demonstrates how to manually validate the data for a given entity class. In the following example, a Book entity class that is annotated with Bean Validation constraints is manually instantiated and validated using the Validator API:

ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); Validator validator = factory.getValidator(); Book book = new Book(); book.setId(BigDecimal.ONE); book.setTitle("The Best Java Book"); Set<ConstraintViolation<Book>> violations = validator.validate(book); for(ConstraintViolation<Book> violation: violations){     System.out.println(violation.getMessage()); }

10.8.3 How It Works

Bean Validation is typically automatically invoked within a Jakarta EE environment. For instance, if using JSF, the validation occurs during the “Process Validations” phase automatically when a form is submitted either synchronously or asynchronously via Ajax. In some cases, it is useful to have the option to call upon the Validator API manually. This can be useful in a Java SE environment or perhaps when writing unit tests.

To invoke the Validator API, first create a ValidatorFactory by calling upon Validation.buildValidatorFactory(). Next, use the factory to generate a Validator. Lastly, validate a bean by calling upon the validator’s validate() method , passing the bean to be validated. This method will return a Set of ConstraintViolation objects. You can then iterate over each of the returned validation errors, obtaining each by calling upon the ConstraintViolation.getMessage() method .

10.9 Grouping Validation Constraints

10.9.1 Problem

You wish to group a number of validation constraints together, such that an entire group of validations can occur at the same time.

10.9.2 Solution

Groups can be applied to constraint annotations by specifying the group(s) to which the annotation belongs via the groups annotation attribute. Groups themselves are generated via Java interfaces. The following interface defines the BookGroup:

public interface BookGroup { }

The BookGroup group can be applied to one or more constraint annotations by specifying the interface within the groups annotation attribute, as seen in the following example:

. . . @Entity @Table(name = "BOOK") @NamedNativeQuery(     name="allBooks",     query = "select id, title, description " +                 "FROM BOOK " +                 "ORDER BY id",     resultClass=Book.class) @NamedQueries({     @NamedQuery(name = "Book.findAll", query = "SELECT b FROM Book b")}) @XmlRootElement @ValidNumChapters public class Book implements Serializable {     private static final long serialVersionUID = 1L;     @Id     @Basic(optional = false)     @NotNull     @Column(name = "ID")     private BigDecimal id;     @JavaBookTitle(message = "Book Title Should Contain The Word Java")     @Size(max = 150, message="The title cannot exceed {max} characters, current title is $'{validatedValue}'",           groups={BookGroup.class})     @Column(name = "TITLE")     protected String title;     @Size(max = 500)     @Column(name = "IMAGE")     private String image;     @NotNull(groups={BookGroup.class})     @Column(name = "NUM_CHAPTERS")     private int numChapters;     @Column(name = "NUM_PAGES")     private int numPages;     @Lob     @NotNull(groups={BookGroup.class})     @Column(name = "DESCRIPTION")     private String description;     @Column(name = "PUBLISH_DATE")     private LocalDate publishDate ;     @ManyToMany(mappedBy="books")     private Set<BookAuthor> authors;     @OneToMany(mappedBy="book", cascade=CascadeType.ALL)     private List<Chapter> chapters = null;     public Book() {     }. . .

Once the group has been put into place, validation can occur against a group, which would cause every constraint annotation that is assigned to that group to be validated. The following is a brief example of how one could utilize the Validation API to validate on a group basis:

ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); Validator validator = factory.getValidator(); . . . Set<ConstraintViolation<Book>> violations = validator.validate(book, "bookGroup"); for(ConstraintViolation<Book> violation: violations){     System.out.println(violation.getMessage()); }

10.9.3 How It Works

Applying a group to a constraint annotation allows that annotation to become part of a grouping with other annotations to which the same group is applied. Formulating groups of annotations can be beneficial when performing tasks in which a specified set of constraints should always be validated. Validation can occur at the group level, thereby validating constraint groups as needed.

To create a group, one must utilize an interface. The Java interface should be empty and acts as a placeholder for the group. The group can be applied to a constraint annotation by specifying the annotation’s groups attribute and passing a list of groups to it. The group attribute accepts one or more groups in the list. The example demonstrates a single group, named BookGroup, being applied to the annotation constraint:

@NotNull(groups={BookGroup.class}) @Column(name = "NUM_CHAPTERS")

To validate a group of constraints, pass the group or groups for validation to the Validator validate() method. In the example, the single BookGroup group is validated, but if there were more groups to be validated, the following syntax would come into play:

validator.validate(book, "group1", "group2");