Using Hibernate Validator to Cover Your Validation Needs
Join the DZone community and get the full member experience.
Join For FreeRecently I had to choose a validation framework or write one by myself. First I thought, no big deal, validation is not a complicated issue. But the more you think about it, the more you come to the conclusion that it is not as shallow as you think – you need to validate different types, you have different groups and many more issues… In short, writing a validation framework by yourself demands a lot of work.
Luckily, JSR 303 solves this and the Hibernate implementation of the JSR does a pretty good job.
- Hibernate Validator is a JSR 303 implementation for bean validation.
- The way to work with this framework is first, to define constraints for java bean fields, and then, validating the bean.
JSR 303
- JSR 303 – defines a metadata model and API for entity validation.
- The default metadata source is annotations, with the ability to override and extend the meta-data through the use of XML.
- The API is not tied to a specific application tier or programming model.
- It is specifically not tied to either the web tier or the persistence tier, and is available for both server-side application programming, as well as for rich client Swing application developers.
Hibernate Validator features
- Defining validation data using annotation and/or XML.
- Full object validation (including inner objects using recursion)
- Create customized constraints and validators.
- Customized error messages.
- Define groups(profiles).
- Create a Traversable Resolver.
Using constraints
Using XML
- Here is a simple example of a constraint using xml:
<constraint-mappings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jboss.org/xml/ns/javax/validation/mapping validation-mapping-1.0.xsd" xmlns="http://jboss.org/xml/ns/javax/validation/mapping"> <default-package>com.mytest</default-package> <bean class="MyBean" > <field name="x" > <constraint annotation="javax.validation.constraints.NotNull"/> </field> <field name="y" > <constraint annotation="javax.validation.constraints.Min"> <element name="value">2</element> </constraint> </field> </bean></constraint-mappings>
- In this example the field x can not be null.
- The field y can not be less than 2. Notice that the “Min” constraint has inner element – “value”.
- Notice the tag “<default-package>”. It indicates the root path of all the beans.
- See also directions on how to load the XML file while using the validator.
Using annotations
- Here is a simple code example:
public class MyBean{@NotNullString x;@Min(2)int y;}
- This example is the equivalent to the previous xml example.
Using both
- Using both annotations and XML constraints is possible.
- By default if you are using both only the XML is taken, unless you are using the attribute ‘ignore-annotations’ in the XML.
- Example:
<constraint-mappings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jboss.org/xml/ns/javax/validation/mapping validation-mapping-1.0.xsd" xmlns="http://jboss.org/xml/ns/javax/validation/mapping"> <default-package>com.mytest.beans</default-package> <!-- ignore-annotations default is true. After changing to 'false', by default, annotations will be stronger --> <bean class="MyBean" ignore-annotations="false" > <field name="x1" > <!-- ignore-annotations default is false, annotation is stronger --> <constraint annotation="javax.validation.constraints.Min"> <element name="value">2</element> </constraint> </field> <field name="x2" ignore-annotations="true" > <!-- XML is stronger --> <constraint annotation="javax.validation.constraints.Min"> <element name="value">2</element> </constraint> </field> </bean>
- Notice that the attribute ignore-annotations appears twice – for a bean and for a field.
- The default for a bean is ignore-annotations=”true” – this means that if you have an XML constraint for a bean, it will cancel the attribute constraint, Unless you will indicate that by ignore-annotations=”false” (look at the example).
- The default for a field is ignore-annotations=”false”. This means that by default annotations for a field are stronger (this is of course after you indicated that that the bean itself wont ignore annotations). If you wont that the XML will be stronger than you have to indicate that by ignore-annotations=”true” (look at the example in the “x2″ constraint).
Existing constraints
- These constraints are a part of the hibernate validation framework:
Constraint path | Parameters |
---|---|
javax.validation.constraints.AssertTrue | (none) |
javax.validation.constraints.AssertFalse | (none) |
javax.validation.constraints.NotNull | (none) |
javax.validation.constraints.Null | (none) |
javax.validation.constraints.Max | value(mandatory) |
javax.validation.constraints.Min | value(mandatory) |
javax.validation.constraints.DecimalMax | value(mandatory) |
javax.validation.constraints.DecimalMin | value(mandatory) |
javax.validation.constraints.Pattern | regexp(mandatory) flags(optional) |
javax.validation.constraints.Past | (none) |
javax.validation.constraints.Future | (none) |
javax.validation.constraints.Size | min(optional) max(optional) |
javax.validation.constraints.Digits | integer(mandatory) fraction(mandatory) |
org.hibernate.constraints.Email | (none) |
org.hibernate.constraints.Length | min(optional) max(optional) |
org.hibernate.constraints.NotEmpty | (none) |
org.hibernate.constraints.Range | min(optional) max(optional) |
Inner objects constraints
- If you have nested beans (beans which contain other beans) you can easily let the system validate also the inner objects by using the constraint ‘valid’.
- XML example:
<constraint-mappings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jboss.org/xml/ns/javax/validation/mapping validation-mapping-1.0.xsd" xmlns="http://jboss.org/xml/ns/javax/validation/mapping"> <default-package>com.mytest.beans</default-package> <bean class="MyBean" ignore-annotations="false" > <field name="innerBean"> <valid/> <!-- validation for an inner object --> </field> </bean> <bean class="InnerBean" ignore-annotations="false"> <field name="xx"> <constraint annotation="javax.validation.constraints.NotNull"/> </field> </bean></constraint-mappings>
- Annotation example:
public class MyBean{@Valid //inner bean to be validated separatelyprivate InnerBean innerBean; public InnerBean getInnerBean() {return innerBean;}public void setInnerBean(InnerBean innerBean) {this.innerBean = innerBean;}}public class InnerBean { @NotNullString xx; public String getXx() {return xx;}public void setXx(String xx) {this.xx = xx;}}
Validating
- Example of a simple validation:
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();Validator validator = factory.getValidator();Set<ConstraintViolation<MyBean>> constraintViolations = validator.validate(bean);
Loading a constraints XML file
- As mentioned, you don’t have to use an XML file for defining constraints, you can just use annotations. But if you do want an XML file, you will have to load the file.
- Example:
Configuration<?> config = Validation.byDefaultProvider().configure();FileInputStream in = new FileInputStream( new File("resources/demo-constraints.xml"));config.addMapping(in);// Building the customized factory// (along with the changed configuration)ValidatorFactory factory = config.buildValidatorFactory();Validator validator = factory.getValidator();
The result
- The result(as you can see in the example above) is a collection of ConstraintViolation.
- Each ConstraintViolation holds the problematic field, it’s value and the error message itself.
- Example of reading the result:
Set<ConstraintViolation<MyBean>> constraintViolations = validator.validate(bean);//printing the resultsfor (ConstraintViolation<MyBean> constraintViolation : constraintViolations) {System.out.println(constraintViolation.getPropertyPath() + " -> " +constraintViolation.getMessage());}
- This object can be easily transformed to a more generic object like ValidationException or CyotaSoapException and so on.
Customized constraints and validators
- If you want to create a new constraint you will have to create the constraint annotation interface and the validator class.
Creating the constraint interface
- Here is a simple constraint example
@Target( { METHOD, FIELD, ANNOTATION_TYPE })@Retention(RUNTIME)@Constraint(validatedBy = MyValidator.class)@Documentedpublic @interface MyConstraint{// These next parameters exist in every constraintString message() default "{com.mytest.MyConstraint.message}";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};// These next parameters are added// They are the constraint's attributesString myOptionalValue() default ""; //this parameter has a default valueString myMustValue(); //this parameters need to get input from the user}
- Notice the @Constraint annotation. It signifies the class that suppose to validates this constraint.
- Notice the message class member. It holds an error message or, like in this case, an error code. It will later be interpreted as a literal error message.
- The payload member holds payload objects. These objects carry additional data attached to the constraints that can be fetched when validating.
Nested constraints
- You can also overload constraints very easily.
- For example, let’s say I want to create a new constraint which also checks that the value is not null.
In this case, all I have to do is this:
@Target( { METHOD, FIELD, ANNOTATION_TYPE })@Retention(RUNTIME)@Constraint(validatedBy = MyValidator.class)@Documented@NotNullpublic @interface MyConstraint{String message() default "{com.mytest.MyConstraint.message}";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};}
- In the example, notice the @NotNull annotation.
Creating the validator class
- Here is an example of a simple validator:
public class MyValidatorimplements ConstraintValidator<MyConstraint, String> {MyConstraint MyConstraint;/** * This function recives the constraint instance * (along with the user values) */public void initialize(MyConstraint MyConstraint) {this.MyConstraint=MyConstraint;}/** * The value is the actual object instance. * */public boolean isValid(String value, ConstraintValidatorContext arg1) { //using input from the userreturn value!=null && value.startsWith(MyConstraint.myMustValue());}}
- The above code shows an example of a validator which validates that the value of the given string starts with a given character.
- The validator implements the ConstraintValidator interface.
- Notice that the constraint is given as input to the initialize() function.
- The value itself is input to the isValid() function
Customizing error messages
- Each error has an error template.
- The error template is defined in the constraint annotation interface.
- This error template is later translated into an error message.
- The actual error message may be defined in 2 places:
1. Inside the constraint deceleration.
The error message be defined when defining the constraint, whether it you are using XML or annotations.
XML example:
<bean class="MyBean" ignore-annotations="false" > <field name="x" ignore-annotations="true" > <constraint annotation="javax.validation.constraints.Min"> <message>x is too small</message> <!-- message can appear inside the XML --> <element name="value">2</element> </constraint> </field> </bean>
Java example:
public class MyBean{@Min(value = 2, message="x is too small")private String x;public String getX() {return x;}public void setX(String x) {this.x = x;}}
2. Using a separate properties message.
You may want to load a separate properties file containing the error messages according to the error template. loading the messages properties file is done using the validation factory configuration:
Configuration<?> config = Validation.byDefaultProvider().configure();// Using a properties file for customized error messagesFileInputStream in = new FileInputStream(new File("resources/messages.properties"));ResourceBundleMessageInterpolator messageInterpolator =new ResourceBundleMessageInterpolator( new PropertyResourceBundle(in));// Setting a messages properties fileconfig.messageInterpolator(messageInterpolator);in = new FileInputStream(new File("resources/demo-constraints.xml"));config.addMapping(in);// Building the customized factory (along with the changed configuration)ValidatorFactory factory = config.buildValidatorFactory();Validator validator = factory.getValidator();
Using groups
- There may be occasions when you will want to create a single constraint but with different values for different situations.
- For example, let’s say you want to create a username field and let him a minimum constraint. But, one time it will have minimum of 5 characters and another time it will have minimum of 6 characters.
- For cases like this you will want to use groups.
- To do so, you will have to create a group, create constraints for that group and last, validate objects by attaching the group.
- Please follow the steps below
Create a group
- To create a group you simply create a new interface.
example:
public interface MyBeanGroup{}
Create a constraint for the group
- XML example:
<bean class="MyBean" ignore-annotations="false" > <field name="x" > <constraint annotation="javax.validation.constraints.NotNull"> <groups><value>com.mytest.groups.MyBeanGroup</value></groups> </constraint> </field> </bean>
- Annotations example:
public class MyBean{@NotNull(groups = MyBeanGroup.class)private String x; public String getX() {return x;}public void setX(String x) {this.x = x;}}
Validate an object using the group
Set<ConstraintViolation<MyBean>> constraintViolations = validator.validate(bean, MyBeanGroup.class);
The default group
- The default group is javax.validation.groups.Default.
- If you don’t assign a constraint any group it applies to the default group.
- If you are validating an object without using any group, it is validated as a part of the default group.
Groups inheritance
- You can also create an group inheritance tree.
- In this way, if you validate an object using a group it will prefer constraints that are defined to it. But if there are no such constraints, then it will also take the constraints of it’s parents.
- Example, this is a group which inherits the default group:
public interface MyBeanGroupextends javax.validation.groups.Default{}
- All the constraints that are defined for that group will apply to it.
- But also all the constraints which do not apply to any group(and by which apply to the default group), will also apply to it, since it exteds the default group.
Jar dependency
- validation-api-1.0.0.GA.jar
- hibernate-validator-4.0.2.GA
- slf4j-api-1.4.2.jar
- slf4j-simple-1.4.2.jar
- log4j-1.2.15.jar
Only for java5
- jaxb-xjc-2.1.6.jar
- jaxb-impl-2.1.6.jar
- jaxb-api-2.1.jar
- activation-1.1.jar
- geronimo-stax-api_1.0_spec-1.0.1.jar
References
Download demo project
Opinions expressed by DZone contributors are their own.
Comments