Chapter 13. Jakarta Bean Validation
Chapter 13. Jakarta Bean Validation 13.1. About Jakarta Bean Validation Jakarta Bean Validation is a model for validating data in Java objects. The model uses built-in and custom annotation constraints to ensure the integrity of application data. It also offers method and constructor validation to ensure constraints on parameters and return values. The specification is documented in Jakarta Bean Validation 2.0 specification . Hibernate Validator is the JBoss EAP implementation of Jakarta Bean Validation. It is also the reference implementation of the Jakarta Bean Validation 2.0 specification. JBoss EAP is 100% compliant with Jakarta Bean Validation 2.0 specification. Hibernate Validator also provides additional features to the specification. To get started with Jakarta Bean Validation, see the bean-validation quickstart that ships with JBoss EAP. For information about how to download and run the quickstarts, see Using the Quickstart Examples in the JBoss EAP Getting Started Guide . JBoss EAP 7.4 includes Hibernate Validator 6.0.x. Features of Hibernate Validator 6.0.x Jakarta Bean Validation 2.0 defines a metadata model and API for entity and method validation. The default source for the metadata is annotations, with the ability to override and extend the metadata through the use of XML. The API is not tied to any specific application tier or programming model. It is available for both server-side application programming and rich client Swing application development. In addition to bug fixes, this release of Hibernate Validator contains many performance improvements for the most common use cases. As of version 1.1, Jakarta Bean Validation constraints can also be applied to the parameters and return values of methods of arbitrary Java types using the Jakarta Bean Validation API. Hibernate Validator 6.0.x and Jakarta Bean Validation 2.0 require Java 8 or later. For more information, see Hibernate Validator 6.0.17.Final - JSR 380 Reference Implementation: Reference Guide . 13.2. Validation Constraints 13.2.1. About Validation Constraints Validation constraints are rules applied to a Java element, such as a field, property or bean. A constraint will usually have a set of attributes used to set its limits. There are predefined constraints, and custom ones can be created. Each constraint is expressed in the form of an annotation. The built-in validation constraints for Hibernate Validator are listed here: Hibernate Validator Constraints . 13.2.2. Hibernate Validator Constraints Note When applicable, the application-level constraints lead to creation of database-level constraints that are described in the Hibernate Metadata Impact column in the table below. Java-specific Validation Constraints The following table includes validation constraints defined in the Java specifications, which are included in the javax.validation.constraints package. Annotation Property type Runtime checking Hibernate Metadata impact @AssertFalse Boolean Check that the method evaluates to false. Useful for constraints expressed in code rather than annotations. None. @AssertTrue Boolean Check that the method evaluates to true. Useful for constraints expressed in code rather than annotations. None. @Digits(integerDigits=1) Numeric or string representation of a numeric Check whether the property is a number having up to integerDigits integer digits and fractionalDigits fractional digits. Define column precision and scale. @Future Date or calendar Check if the date is in the future. None. @Max(value=) Numeric or string representation of a numeric Check if the value is less than or equal to max. Add a check constraint on the column. @Min(value=) Numeric or string representation of a numeric Check if the value is more than or equal to Min. Add a check constraint on the column. @NotNull Check if the value is not null. Column(s) are not null. @Past Date or calendar Check if the date is in the past. Add a check constraint on the column. @Pattern(regexp="regexp", flag=) or @Patterns( {@Pattern(... )} ) String Check if the property matches the regular expression given a match flag. See java.util.regex.Pattern . None. @Size(min=, max=) Array, collection, map Check if the element size is between min and max, both values included. None. @Valid Object Perform validation recursively on the associated object. If the object is a Collection or an array, the elements are validated recursively. If the object is a Map, the value elements are validated recursively. None. Note The parameter @Valid is a part of the Jakarta Bean Validation specification, even though it is located in the javax.validation.constraints package. Hibernate Validator-specific Validation Constraints The following table includes vendor-specific validation constraints, which are a part of the org.hibernate.validator.constraints package. Annotation Property type Runtime checking Hibernate Metadata impact @Length(min=, max=) String Check if the string length matches the range. Column length will be set to max. @CreditCardNumber String Check whether the string is a well formatted credit card number, derivative of the Luhn algorithm. None. @EAN String Check whether the string is a properly formatted EAN or UPC-A code. None. @Email String Check whether the string is conform to the e-mail address specification. None. @NotEmpty Check if the string is not null nor empty. Check if the connection is not null nor empty. Columns are not null for String. @Range(min=, max=) Numeric or string representation of a numeric Check if the value is between min and max, both values included. Add a check constraint on the column. 13.2.3. Using a Jakarta Bean Validation Custom Constraint Jakarta Bean Validation API defines a set of standard constraint annotations, such as @NotNull , @Size , and so on. However, in cases where these predefined constraints are not sufficient, you can easily create custom constraints tailored to your specific validation requirements. Creating a Jakarta Bean Validation custom constraint requires that you create a constraint annotation and implement a constraint validator . The following abbreviated code examples are taken from the bean-validation-custom-constraint quickstart that ships with JBoss EAP. See that quickstart for a complete working example. 13.2.3.1. Creating A Constraint Annotation The following example shows the personAddress field of entity Person is validated using a set of custom constraints defined in the class AddressValidator . Create the entity Person . Example: Person Class package org.jboss.as.quickstarts.bean_validation_custom_constraint; @Entity @Table(name = "person") public class Person implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue @Column(name = "person_id") private Long personId; @NotNull @Size(min = 4) private String firstName; @NotNull @Size(min = 4) private String lastName; // Custom Constraint @Address for bean validation @NotNull @Address @OneToOne(mappedBy = "person", cascade = CascadeType.ALL) private PersonAddress personAddress; public Person() { } public Person(String firstName, String lastName, PersonAddress address) { this.firstName = firstName; this.lastName = lastName; this.personAddress = address; } /* getters and setters omitted for brevity*/ } Create the constraint validator files. Example: Address Interface package org.jboss.as.quickstarts.bean_validation_custom_constraint; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import javax.validation.Constraint; import javax.validation.Payload; // Linking the AddressValidator class with @Address annotation. @Constraint(validatedBy = { AddressValidator.class }) // This constraint annotation can be used only on fields and method parameters. @Target({ ElementType.FIELD, ElementType.PARAMETER }) @Retention(value = RetentionPolicy.RUNTIME) @Documented public @interface Address { // The message to return when the instance of MyAddress fails the validation. String message() default "Address Fields must not be null/empty and obey character limit constraints"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } Example: PersonAddress Class package org.jboss.as.quickstarts.bean_validation_custom_constraint; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.OneToOne; import javax.persistence.PrimaryKeyJoinColumn; import javax.persistence.Table; @Entity @Table(name = "person_address") public class PersonAddress implements Serializable { private static final long serialVersionUID = 1L; @Id @Column(name = "person_id", unique = true, nullable = false) @GeneratedValue(strategy = GenerationType.SEQUENCE) private Long personId; private String streetAddress; private String locality; private String city; private String state; private String country; private String pinCode; @OneToOne @PrimaryKeyJoinColumn private Person person; public PersonAddress() { } public PersonAddress(String streetAddress, String locality, String city, String state, String country, String pinCode) { this.streetAddress = streetAddress; this.locality = locality; this.city = city; this.state = state; this.country = country; this.pinCode = pinCode; } /* getters and setters omitted for brevity*/ } 13.2.3.2. Implementing A Constraint Validator Having defined the annotation, you need to create a constraint validator that is able to validate elements with an @Address annotation. To do so, implement the interface ConstraintValidator as shown below: Example: AddressValidator Class package org.jboss.as.quickstarts.bean_validation_custom_constraint; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import org.jboss.as.quickstarts.bean_validation_custom_constraint.PersonAddress; public class AddressValidator implements ConstraintValidator<Address, PersonAddress> { public void initialize(Address constraintAnnotation) { } /** * 1. A null address is handled by the @NotNull constraint on the @Address. * 2. The address should have all the data values specified. * 3. Pin code in the address should be of at least 6 characters. * 4. The country in the address should be of at least 4 characters. */ public boolean isValid(PersonAddress value, ConstraintValidatorContext context) { if (value == null) { return true; } if (value.getCity() == null || value.getCountry() == null || value.getLocality() == null || value.getPinCode() == null || value.getState() == null || value.getStreetAddress() == null) { return false; } if (value.getCity().isEmpty() || value.getCountry().isEmpty() || value.getLocality().isEmpty() || value.getPinCode().isEmpty() || value.getState().isEmpty() || value.getStreetAddress().isEmpty()) { return false; } if (value.getPinCode().length() < 6) { return false; } if (value.getCountry().length() < 4) { return false; } return true; } } 13.3. Jakarta Bean Validation Configuration You can configure Jakarta Bean Validation using XML descriptors in the validation.xml file located in the /META-INF directory. If this file exists in the class path, its configuration is applied when the ValidatorFactory gets created. Example: Jakarta Bean Validation Configuration File The following example shows several configuration options of the validation.xml file. All the settings are optional. These options can also be configured using the javax.validation package. <validation-config xmlns="http://jboss.org/xml/ns/javax/validation/configuration" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jboss.org/xml/ns/javax/validation/configuration"> <default-provider> org.hibernate.validator.HibernateValidator </default-provider> <message-interpolator> org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator </message-interpolator> <constraint-validator-factory> org.hibernate.validator.engine.ConstraintValidatorFactoryImpl </constraint-validator-factory> <constraint-mapping> /constraints-example.xml </constraint-mapping> <property name="prop1">value1</property> <property name="prop2">value2</property> </validation-config> The node default-provider allows to choose the Jakarta Bean Validation provider. This is useful if there is more than one provider on the classpath. The message-interpolator and constraint-validator-factory properties are used to customize the used implementations for the interfaces MessageInterpolator and ConstraintValidatorFactory , which are defined in the javax.validation package. The constraint-mapping element lists additional XML files containing the actual constraint configuration.
package org.jboss.as.quickstarts.bean_validation_custom_constraint; @Entity @Table(name = "person") public class Person implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue @Column(name = "person_id") private Long personId; @NotNull @Size(min = 4) private String firstName; @NotNull @Size(min = 4) private String lastName; // Custom Constraint @Address for bean validation @NotNull @Address @OneToOne(mappedBy = "person", cascade = CascadeType.ALL) private PersonAddress personAddress; public Person() { } public Person(String firstName, String lastName, PersonAddress address) { this.firstName = firstName; this.lastName = lastName; this.personAddress = address; } /* getters and setters omitted for brevity*/ } package org.jboss.as.quickstarts.bean_validation_custom_constraint; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import javax.validation.Constraint; import javax.validation.Payload; // Linking the AddressValidator class with @Address annotation. @Constraint(validatedBy = { AddressValidator.class }) // This constraint annotation can be used only on fields and method parameters. @Target({ ElementType.FIELD, ElementType.PARAMETER }) @Retention(value = RetentionPolicy.RUNTIME) @Documented public @interface Address { // The message to return when the instance of MyAddress fails the validation. String message() default "Address Fields must not be null/empty and obey character limit constraints"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } package org.jboss.as.quickstarts.bean_validation_custom_constraint; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.OneToOne; import javax.persistence.PrimaryKeyJoinColumn; import javax.persistence.Table; @Entity @Table(name = "person_address") public class PersonAddress implements Serializable { private static final long serialVersionUID = 1L; @Id @Column(name = "person_id", unique = true, nullable = false) @GeneratedValue(strategy = GenerationType.SEQUENCE) private Long personId; private String streetAddress; private String locality; private String city; private String state; private String country; private String pinCode; @OneToOne @PrimaryKeyJoinColumn private Person person; public PersonAddress() { } public PersonAddress(String streetAddress, String locality, String city, String state, String country, String pinCode) { this.streetAddress = streetAddress; this.locality = locality; this.city = city; this.state = state; this.country = country; this.pinCode = pinCode; } /* getters and setters omitted for brevity*/ } package org.jboss.as.quickstarts.bean_validation_custom_constraint; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import org.jboss.as.quickstarts.bean_validation_custom_constraint.PersonAddress; public class AddressValidator implements ConstraintValidator<Address, PersonAddress> { public void initialize(Address constraintAnnotation) { } /** * 1. A null address is handled by the @NotNull constraint on the @Address. * 2. The address should have all the data values specified. * 3. Pin code in the address should be of at least 6 characters. * 4. The country in the address should be of at least 4 characters. */ public boolean isValid(PersonAddress value, ConstraintValidatorContext context) { if (value == null) { return true; } if (value.getCity() == null || value.getCountry() == null || value.getLocality() == null || value.getPinCode() == null || value.getState() == null || value.getStreetAddress() == null) { return false; } if (value.getCity().isEmpty() || value.getCountry().isEmpty() || value.getLocality().isEmpty() || value.getPinCode().isEmpty() || value.getState().isEmpty() || value.getStreetAddress().isEmpty()) { return false; } if (value.getPinCode().length() < 6) { return false; } if (value.getCountry().length() < 4) { return false; } return true; } } <validation-config xmlns="http://jboss.org/xml/ns/javax/validation/configuration" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jboss.org/xml/ns/javax/validation/configuration"> <default-provider> org.hibernate.validator.HibernateValidator </default-provider> <message-interpolator> org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator </message-interpolator> <constraint-validator-factory> org.hibernate.validator.engine.ConstraintValidatorFactoryImpl </constraint-validator-factory> <constraint-mapping> /constraints-example.xml </constraint-mapping> <property name="prop1">value1</property> <property name="prop2">value2</property> </validation-config>