SQL Zone is brought to you in partnership with:

Avi is a senior developer with a passion for Java technologies… http://www.aviyehuda.com/ Avi is a DZone MVB and is not an employee of DZone and has posted 25 posts at DZone. You can read more from them at their website. View Full User Profile

Using Hibernate Validator to Cover Your Validation Needs

04.15.2010
| 70029 views |
  • submit to reddit

Recently 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{
@NotNull
String 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 separately
private InnerBean innerBean;

public InnerBean getInnerBean() {
return innerBean;
}

public void setInnerBean(InnerBean innerBean) {
this.innerBean = innerBean;
}

}

public class InnerBean {

@NotNull
String 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 results
for (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)
@Documented
public @interface MyConstraint{

// These next parameters exist in every constraint
String message() default "{com.mytest.MyConstraint.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};

// These next parameters are added
// They are the constraint's attributes
String myOptionalValue() default ""; //this parameter has a default value
String 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
@NotNull
public @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 MyValidator
implements 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 user
return 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 messages
FileInputStream in =
new FileInputStream(new File("resources/messages.properties"));

ResourceBundleMessageInterpolator messageInterpolator =
new ResourceBundleMessageInterpolator(
new PropertyResourceBundle(in));

// Setting a messages properties file
config.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 MyBeanGroup
extends 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

 

from http://www.aviyehuda.com/

Published at DZone with permission of Avi Yehuda, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Bogdan Marian replied on Wed, 2010/04/21 - 2:51am

Thank you very much for this great article!

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.