Validating Domain Objects in Hibernate Part 3: Creating Your Own Validator
Posted on September 27, 2007 by Scott Leberknight
This is the third in a series of short blogs describing how the Hibernate Validator allows you to define validation rules directly on domain objects where it belongs. In the second article I showed how to configure the Hibernate Validator and showed event-based and manual validation of domain objects. Here I'll show you how to create your own validators.
The most common use case is validation of individual properties on your domain objects. For example, is a property required; does it need to match a specific pattern; must it have a minimum and maximum length; or must it be a valid credit card number? Hibernate Validator makes creating validators for specific properties easy. In addition to property-specific validators, you can also write class-level validators. For example, maybe you allow certain fields to be blank, but if one of them is entered, then several others are required as well. You annotate property-level validators on properties (i.e. getter methods) and class-level validators on your domain object itself. Here's a short example showing both types of validator:
@Entity @UserNameConfirmation public class User extends BaseEntity { // id, version, etc. are defined in BaseEntity private String userName; private String firstName; private String lastName; private String ssn; @Required @Email public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } @Email @Transient public String getUserNameConfirmation() { return userNameConfirmation; } public void setUserNameConfirmation(String userNameConfirmation) { this.userNameConfirmation = userNameConfirmation; } @Required @Length(min = 2, max = 50) public String getFirstName () { return firstName; } public void setFirstName() { this.firstName = firstName; } @Required @Length(min = 2, max = 50) public String getLastName () { return lastName; } public void setLastName() { this.lastName = lastName; } @Pattern(regex = "[0-9]{3}-[0-9]{2}-[0-9]{4}") public String getSsn() { return ssn; } public void setSsn(String ssn) { this.ssn = ssn; } }
In the above example, there is a UserNameConfirmation
annotation at the class level. This applies a validator that ensures a user name and a confirmation user name match, but only if the confirmation user name is supplied. There are also several property-level validators being applied: Email
, Required
, Length
, and Pattern
. I've also marked the userNameConfirmation
property as transient with the @Transient
annotation, since this is not actually a persistent property and we want Hibernate to ignore it during persistence operations; it is used only for validation purposes.
So let's actually create a validator now. The @Required
validator I've been using in the example code is not one of the Hibernate validators that come out of the box. It has several other ones that are similar (@NotNull
and @NotEmpty
) but that I don't use in practice - more on why not in the next article though. To create a validator, you only need to do two things:
- Define a validation annotation
- Implement the validator class
Without further ado, here is the @Required
annotation definition:
package com.nearinfinity.hibernate.validator;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import org.hibernate.validator.ValidatorClass;
/**
* Designates a property that is required to have a value. It cannot be null or empty. The default message is
* "validator.required" which is assumed to exist in the expected resource bundle
* ValidatorMessages.properties
.
*/
@Documented
@ValidatorClass(RequiredValidator.class)
@Target( { METHOD, FIELD })
@Retention(RUNTIME)
public @interface Required {
String message() default "{validator.required}";
}
The important things in this code are:
- You specify the validator class (the class that performs the actual validation logic) using the
ValidatorClass
annotation, supplying the validator class implementation as the sole argument. - You need to tell Java where the annotation is permitted via the
Target
annotation. We'll allow the@Required
annotation on methods and fields. - You need to tell Java to retain this annotation at runtime via the
@Retention
annotation, since Hibernate uses reflection to determine the validators for domain objects. - You specify a
message
parameter with a default value with value "validator.required" and which is internationalized in theValidatorMessages.properties
resource bundle.
If there are additional parameters you need for your own validator, you can specify them in your annotation. For example, the Hibernate @Length
validation annotation specifies a min and a max, like this:
@Documented @ValidatorClass(LengthValidator.class) @Target({METHOD, FIELD}) @Retention(RUNTIME) public @interface Length { int max() default Integer.MAX_VALUE; int min() default 0; String message() default "{validator.length}"; }
Now that we've defined the @Required
validator annotation, we implement the actual validator class:
public class RequiredValidator implements Validator<Required>, Serializable {
public void initialize(Required parameters) {
// nothing to initialize
}
/** @return true
if the value is not null or an empty String */
public boolean isValid(Object value) {
if (value == null) {
return false;
}
if (value instanceof String) {
String stringValue = (String) value;
if (stringValue.trim().length() == 0) {
return false;
}
}
return true;
}
}
The important points in the validator implementation class are:
- The validator must implement the Hibernate
Validator
interface, which is parametrized by the validator annotation. - You should also implement
Serializable
.
The Validator
interface is very simple consisting of only two methods: initialize
and isValid
. The initialize
method allows custom initialization, for example you can initialize parameters defined in your validation annotation such as the "min" and "max" in the Hibernate @Length
annotation. The isValid
method does the real work and validates the object supplied as an argument. That's pretty much it. Implementing class-level validators like the @UserNameConfirmation
annotation I showed in an earlier example works exactly the same way. About the only difference is that you probably will restrict the annotation to types, i.e. classes using @Target({ TYPE })
. Now, all that's left is to annotate your domain objects!
In the next article I'll explain my earlier statement that I don't use the @NotNull
or @NotEmpty
annotations in my domain objects.