This article talks about the process of validation in Java EE, more specifically about Hibernate Validation and Bean Validation. We start by describing why we need validation, what solutions are available, how we use them and why they are great. We then proceed to describe their limitations, and offer proposals for resolving those limitations in the hope that the future Bean Validation standard will incorporate our (or similar) solutions.
If you are writing your database persistence interface in Java, chances are you will be using the Java persistence API. It abstracts database tables as Java classes (entities in the local jargon), database table columns as object properties (in the Java Bean sense), and database table rows as instances of those classes. The mapping between Java classes and the database model is done using annotations.
This API (javax.persistence) defines a few ways to put constraints on your properties, for instance column or multi-column unicity, or nullability of columns.
If you try to save (persist or merge in the local jargon) entities in which those constraints have been violated, you will get an ugly exception, and the transaction will probably be rolled back (if that is what you asked for).
When I say ugly exception I mean an exception which has no clear semantics as to what the exception class is, how to get to it (they nest exceptions in non-standard way), how to get to the name of the violated constraint(s) (which incidentally cannot be named by hand in the annotation), and does not allow an application to specify a message to display to signal errors of a particular constraint.
Additionally, it is not possible with Java persistence to specify custom validation rules for entities (the whole entity) or properties.
Hibernate provides ways to do custom validation for entities and properties. Work is under way to standardise Hibernate Validation in JSR-303 which is discussed openly and actively in this forum. We will discuss this JSR further in this article.
We should start with a simple example of how to define and edit an entity with JPA, Hibernate and our framework of choice: Seam.
Here we start simple by having only one interesting property: emailAddress which should not be null, and should be a valid email address. Hibernate Validation declares validation constraints using Java annotations, which can be put on entity bean property fields or getter methods, much in the same way that you define your database mappings in JPA.
Hibernate Validation comes with many predefined validation constraint annotations, and provides a way to define your own if you need anything else. To solve our problem we are going to use the @NotNull and @EMail annotation constraints on our emailAddress property which will declare that this property must not be null and must be a valid email address:
Person.java
@Entity
public class Person {
@GeneratedValue
@Id
private long id;
@NotNull
@EMailValidator
private String emailAddress;
/* Follows getter/setter methods */
}
If we try to persist a Person with a null or invalid email address, the JPA layer will throw an InvalidStateException (because Hibernate Validation enforces validation constraints automatically when saving an entity). This exception will possibly be wrapped by a transaction exception. But these validation constraints are more useful for other reasons:
Here in the view where we edit a Person we instruct Seam to validate each field (the <s:validateAll> tag), and place the validation messages after each invalid field (the afterInvalidField facet). This will use the validation constraints we defined in the Entity to validate the values during the Process Validations JSF phase.
edit.xhtml
...
<h:form>
<s:validateAll>
<f:facet name="afterInvalidField">
<s:message/>
</f:facet>
<p>
<label for="emailAddress">E-mail address</label>
<br/>
<s:decorate>
<h:inputText id="emailAddress" value="#{editedPerson.mailAddress}"/>
</s:decorate>
</p>
<p>
<h:commandButton action="#{personAction.save}" value="Save"/>
<h:outputText value=" or "/>
<h:commandLink action="#{personAction.cancel}">Cancel</h:commandLink>
</p>
</s:validateAll>
</h:form>
...
As you can see it is quite straightforward.
This is the code which will be invoked on the server when the view described above will be invoked. It uses the personID request parameter (injected by Seam using the @RequestParameter annotation) to load a Person object from the database and outject it in the editedPerson variable using a factory method (initEditedPerson) which will be invoked when editedPerson is null.
Once the editedPerson has been sent to the view for editing, there are two ways out of the view: canceling (calling the cancel() method) or saving (calling save()). Canceling simply does nothing, and saving is as straightforward as instructing the persistence layer (PersonDAO) to save our entity in the database, since it has already been automatically validated.
PersonActionBean.java
@Stateful
@Name("personAction")
public PersonActionBean implements PersonAction {
@RequestParameter
private Long personID;
@In(required = false)
@Out
private Person editedPerson;
@EJB
private PersonDAO personDAO;
@Begin
@Factory("editedPerson")
private void initEditedPerson(){
editedPerson = personDAO.findPersonById(personID);
}
@End
public void cancel(){}
@End
public void save(){
personDAO.save(editedPerson);
}
}
Now that the basics about Hibernate Validation have been explained, we still have to explain two important features: custom validation constraints, and custom messages.
We have noticed that users using our application were able to save local email addresses (email addresses which do not contain an @ or have a host name with no domain after it). These local email addresses are widely used in local or private networks, and are perfectly valid email addresses, but they cannot be used outside of those networks, so they cannot be reached globally, which means we cannot contact those people.
The @EMail constraint validation will accept both local and global email addresses, because they are both valid, which is why these users have been able to submit those local email addresses. So we have to define our own validation constraint which will refuse local email addresses.
This is very easy to do in Hibernate Validation: we have to define a new annotation (@NonLocalEmail) which will be used on our property, and point to a class responsible for the validation (NonLocalEmailValidator):
NonLocalEmail.java
@Documented
@ValidatorClass(NonLocalEmailValidator.class)
@Target({ ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface NonLocalEmail {
String message() default "{validator.email}";
}
There are several points of interest in the previous annotation:
As for the actual class containing the validation logic, we will simply extend the EmailValidator class to add a check on the domain-part of the email address:
NonLocalEmailValidator.java
public class NonLocalEmailValidator extends EmailValidator
implements Validator<NonLocalEmail> {
public void initialize(NonLocalEmail annotation){}
public void isValid(Object email){
// null values are validated by other validators
if(email == null)
return true;
boolean validEmail = super.isValid(email);
// if the address is not even a valid email,
// it cannot possibly be a valid non-local email
if(!validEmail)
return false;
// now check that it has a domain part
String emailValue = (String)email;
// does it have an '@' sign?
int atIndex = emailValue.indexOf('@');
if(atIndex == -1)
return false;
// does it have a fully qualified domain name after it?
return emailValue.indexOf('.', atIndex+1) != -1;
}
}
As you can see, all we have to do is implement the Validator interface, and define two methods. The initialize method is used if our validator logic can be parameterised by the constraint annotation, which is not the case here. The isValid method takes a value and checks whether this value is a valid non-local email address. All very straightforward and incredibly nice.
Now that we hope to have convinced you that Hibernate Validation is the way to go because it is so nice and allows you to not duplicate your validation code, we have to admit it has a number of limitations that we've hit (not theoretical limitations, but limitations that forced us into duplicating our code and bypassing the automatic validation integration we've described earlier with Seam).
JPA actually comes with several constraints declarations such as:
The duplication of the "NOT NULL" database constraint between JPA and Hibernate Validation is not merely unfortunate:
Furthermore, for the same reasons, unicity constraints defined in JPA cannot be localised, generate a different exception, and are not used in Hibernate Validation and Seam. What is worse though is that they cannot be replaced by an equivalent custom Hibernate Validation constraint for several practical reasons (API problems we can overcome), and one more fundamental and implacable reason: unicity can only be checked reliably while committing the transaction. Indeed, because it depends on other values in the database, nothing prevents other concurrent transactions from modifying other values after you've checked manually for unicity and before you commit your transaction (aside from locking).
Forgetting the fundamental issue, we've attempted to implement our own unicity constraint in Hibernate Validation, for sport mainly, and in order to check if that framework was really capable of providing an alternative to JPA's unicity constraints.
Let us start simply by adding a single-column unicity constraint to Hibernate Validation on our entity:
Person.java
@Entity
public class Person {
...
@NotNull // instead of @Column(nullable = false)
@Unique // instead of @Column(unique = true)
@EMailValidator // no JPA equivalent
private String emailAddress;
...
}
Here is how the @Unique annotation would be defined:
Unique.java
@Documented
@ValidatorClass(UnicityValidator.class)
@Target({ ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Unique {
String message() default "{validator.unique}";
}
And here is how we would define the validation class:
UnicityValidator.java
public class UnicityValidator implements Validator<Unique> {
public void initialize(Unique annotation){}
public void isValid(Object value){
// null values are validated by other validators
if(value == null)
return true;
// here we check for unicity by checking if any other entity
// of the same class holds the same value
// ... wait a sec ... how do we do that without having
// the entity and the property we are validating???
Query query = getEntityManager().createQuery("SELECT count(x) FROM ?? x"
+ " WHERE x.?? = :value AND x.id != :id LIMIT 1");
query.setParameter("value", value);
query.setParameter("id", ??);
return ((Number)query.getSingleResult()).intValue() == 0;
}
}
As you can see, we only get the value we are validating (a particular email address), and with only that we simply cannot check for unicity: we need to know the type of object we are validating, and the particular property we are checking for unicity.
There are five ways out of this limitation:
Since we personally dislike the first 3 options, we will discuss the last two:
So we are left with the option of doing both. Extending Hibernate Validation to provide more information about the bean and the annotated property for property-level constraints and extending Seam so that bean-level validation is executed during the Process Validations lifecycle. Any bean-level validation error messages would be displayed globally rather than next to the edited field since they cannot be associated to a particular property.
Our modifications in Hibernate Validation is very straight-forward and backwards-compatible: we define a subclass of Validator called ExtendedValidator which provides us with the appropriate information when validating:
public interface ExtendedValidator<A extends Annotation> extends Validator<A> {
/**
* Returns true if the given bean's property can be set to the given value
*/
public boolean isValid(Object bean, String propertyName, Object value);
}
This method is then invoked in ClassValidator when the validator happens to implement ExtendedValidator. This makes sure that all previously-defined validators still work. We then have to overload the Validator.getPotentialInvalidValues method with an extra parameter for the bean instance, which we use in Seam.
In Seam we then still have to invoke bean validation, but this is at odds with the approach during the Process Validations lifecycle, which does not set the bean properties but uses Validator.getPotentialInvalidValues to check for validity without touching the bean instance. Because there is no equivalent potent equivalent to bean-level validation, something deeper has to change. What then? We're not sure, but we're still working on the solution.
Additionally, it would be really nice if bean-level validation could specify different error messages for different errors, as well as specify more than one error (not just returning false) and map errors to property names, so that a bean-level validator could do just as much as property-level validators.
When we integrated web services in our application, we decided to go with bean validation all the way, and get rid of all the validation (validation: not permissions) in the web services frontend. We simply attempt to use whatever the client gives us and the validating persistence layer will complain if needed, at which point we can easily access the error message and format it for the web service client. We just had to move the validation from the action beans into the persistence tier.
While this seems like a good idea, our validation is actually more complex than what we abstracted into the entity beans. In other words, we got hit by limitations in Hibernate Validation. This includes what has been described earlier: the lack of bean-level validation in Seam, its limitations in error reporting and property association, and the lack of support for unicity constraints in Hibernate Validation.
We also discovered that we had more fundamental problems with validation: our beans actually have states, in the sense than some properties should be validated differently based on other properties, thus creating a dependency graph. To give you an example, suppose we add SMS integration in our system, and users can now be contacted either by email, SMS, or both, but having neither email nor SMS is invalid. How can we validate this?
Person.java
@Entity
public class Person {
...
@NotNull // iff phoneNumber is null
@Unique
@EMailValidator
private String emailAddress;
@NotNull // iff emailAddress is null
@Unique
@PhoneNumberValidator
private String phoneNumber;
...
}
Clearly with property-level validation there is no way to specify that one of those can be null if and only if the other is not null. We are left with bean-level validation, which has several drawbacks:
Not happy with bean-level validation, we set out to declare that we need conditional validation: validation which is enabled or disabled based on some conditions. Just at the same time, we noticed that there is a JSR under way to standardise Bean Validation: JSR-303, so we set out to look at it and see if it solves our problems.
JSR-303 is a new JSR which is aimed at providing a standard way to validate a Java Bean. It is edited by Emmanuel Bernard (author of Hibernate Validation), and consists mainly in abstracting Hibernate Validation from its Hibernate dependency so that it can be used to validate any Java Bean, persistent or not. In a spirit of transparency and openness, he has opened a forum where everyone can give feedback on the JSR as it evolves. We should point out that we think Emmanuel Bernard is doing great work, not only because we're using software he wrote, but also because he's doing us all a favour by standardising Bean Validation, which is something that will prove very useful once people understand its full potential.
The main differences we see with Hibernate Validation are validation groups and partial validation, which provides a way to declare several "layers" or "sets and subsets" of validation, that can be enabled or disabled when validating programmatically. The JSR also defines an entire reflection framework to access the validation constraints at run-time.
While it may seem that validation groups are what we need, they are different in that they do not specify the conditions for which those groups should be enabled or not.
We proposed a framework for conditional validation which provides several types of conditions:
Validation conditions are referred to by name in validation constraints that want to depend on those conditions. For example, the @NotNull(validationConditions={"admin"}) validation constraint is only enabled iff the admin validation condition is true.
Validation conditions are then defined in various flavours, depending on the condition they are checking. The most simple example is the @ValidationConditionOnTrue which defines a validation condition on a bean property which evaluates to true:
@ValidationConditionOnTrue
public class User {
// simple check for admin user
@ValidationConditionOnTrue(name = "admin")
private boolean admin;
// sometimes it can be more complicated
private List<Permission> permissions = new ArrayList<Permission>();
@ValidationConditionOnTrue(name = "admin")
public boolean hasAdminPermission(){
for(Permission p : permissions)
if(p.isAdmin())
return true;
return false;
}
// now that we have defined two ways that the validation condition "admin"
// could be true, we can use it
// we require administrators to have a valid email address. other users may
// have one too, but it is not required.
@NotNull(validationConditions = {"admin"})
@Email
private String emailAddress;
}
Now that we are familiar with the difference between referencing and defining a validation condition, let us look at more complex validation conditions, such as the @ValidationConditionOnUEL which defines a validation condition based on a Unified Expression Language (UEL) expression. This is very useful for checks on properties located anywhere within the bean, multiple properties, or even sub-properties. With this we can accomplish our minimum requirement of having at least one of the postAddress or emailAddress properties set:
@ValidationConditionOnUEL
public class User {
// we want emailAddress to be set if postAddress isn't set
@NotNull(validationConditions = {"noPostAddress"})
@Email
// define a validation condition true if emailAddress is not set
@ValidationConditionOnUEL(name = "noEmailAddress", uel = "emailAddress == null")
private String emailAddress;
// we want postAddress to be set if emailAddress isn't set
@NotNull(validationConditions = {"noEmailAddress"})
@Valid
// define a validation condition true if postAddress is not set
@ValidationConditionOnUEL(name = "noPostAddress", uel = "postAddress == null")
private Address postAddress;
}
Note that since the UEL expression has access to the whole bean, it does not matter really whether the validation condition is placed on a property or on the bean itself. It is simply a matter of style and we prefer to have them located near their source, but naturally, should an UEL expression reference several properties, we would place the validation condition definition on the bean for clarity.
We still have one more type of validation condition to see: a validation condition which depends on the success or failure of other validation. In the previous example, we've used null checks in UEL, but we also defined @NotNull validation constraints. We would like to be able to reuse those constraints so say "validation this property iff this property failed to validate, or succeeded in validating". Because Java Annotations are so limited, we must resort to referencing those validation constraints by name, which we then must assign. Let us redefine the previous example with this new validation condition:
@ValidationConditionOnValue
public class User {
// we want emailAddress to be set if postAddress isn't set
@NotNull(name = "nullEmailAddress", validationConditions = {"noPostAddress"})
@Email
// define a validation condition true if nullPostAddress fails to validate
@ValidationConditionOnValue(name = "noEmailAddress",
failedValidators = {"nullPostAddress"})
private String emailAddress;
// we want postAddress to be set if emailAddress isn't set
@NotNull(name = "nullPostAddress", validationConditions = {"noEmailAddress"})
@Valid
// define a validation condition true if nullEmailAddress fails to validate
@ValidationConditionOnValue(name = "noPostAddress",
failedValidators = {"nullEmailAddress"})
private Address postAddress;
}
Using this last type of validation condition we can even define some dependency: validation which does not make sense if a required validator already failed. In the complex validation required by postal addresses, we want to validate the country code and the post code. But the post code depends on the street and country code, so in order for its validation to be meaningful, we must make sure that the street and country codes have already been validated:
@ValidationConditionOnValue dependency
public class Address {
@NotEmpty(name = "setStreet")
private String street;
@NotNull(name = "setCountryCode")
@ValidCountryCode(name = "validCountryCode")
private String countryCode;
@NotNull
@ValidPostCode(validationConditions = {"validStreetAndCountryCode"})
@ValidationConditionOnValue(name = "validStreetAndCountryCode",
validators = {"setStreet",
"setCountryCode",
"validCountryCode"})
private String postCode;
}
Note that this previous example only makes sense if Validator is fixed so as to include the whole bean instance even for property validation, otherwise the @ValidPostCode cannot access the street and countryCode properties.
Also note that while we would like in some cases to actually include the validation condition's definition in the validation constraints themselves (for instance @NotNull(uel = "otherProperty == null")), it is probably better practice to differentiate condition validation references and definitions so that one definition can be reused in multiple references. Due to the lack of inheritance in annotations, all validation annotations will be required to support two attributes more than the current message attribute: String[] validationConditions : default {} and String name : default "". This is unfortunate, but we don't see any better alternative.
Sometimes it is not enough to support these conditions, and we would like to condition the validation based on some external event outside the scope of the bean itself. For instance, in JPA — which is where Hibernate Validation is applied right now —, we would often like to validate a bean when it is inserted, updated, or before it is deleted. This would then be based on JPA events such as ON_INSERT, ON_UPDATE, ON_DELETE corresponding to the underlying operation done on the entity bean.
Sometimes we also have expensive validation that we would like to only trigger when its costs has to be paid: when the value being checked has changed. For instance when we want to validate an entire collection of entities, we would like to only validate it when that collection has changed. Or to get back to our previous example of postal address validation, we would want to validate the whole address only if any of its property has changed. This can be seen as an additional event in JPA which we can name ON_CHANGE. The validation framework would then be responsible for checking whether the persisted value differs from the validated value, and trigger the validation only if they differ.
Validation Events
@ValidationConditionOnEvent(name = "changed", events = {ValidationEvent.ON_CHANGE})
public class Address {
@NotEmpty(name = "setStreet", validationConditions = {"changed"})
private String street;
@NotNull(name = "setCountryCode", validationConditions = {"changed"})
@ValidCountryCode(name = "validCountryCode", validationConditions = {"changed"})
private String countryCode;
@NotNull(validationConditions = {"changed"})
@ValidPostCode(validationConditions = {"validStreetAndCountryCode", "changed"})
@ValidationConditionOnValue(name = "validStreetAndCountryCode",
validators = {"setStreet",
"setCountryCode",
"validCountryCode"})
private String postCode;
}
We can see from this previous example that we might need some sort of default validation group which would apply to the whole bean or each validation constraint.
Here is an other illustration:
Validation Events 2
// define a condition true when we are going to delete this entity
@ValidationConditionOnEvent(name = "deleted", events = {ValidationEvent.ON_DELETE})
public class User {
// we want to pay the expensive validation of this collection only when it changes
@CheckJobs(validationConditions = {"changedJobs"})
@ValidationConditionOnEvent(name = "changedJobs", events = {ValidationEvent.ON_CHANGE})
private List<Job> runningJobs = new ArrayList<Job>();
// we can only delete this entity if it has no more running jobs
@AssertTrue(validationConditions = {"deleted"})
public boolean hasNoRunningJobs(){
return runningJobs.size() == 0;
}
}
Basically because a validation check (running a validator's isValid) method should be side-effect-free, we can take the simplistic view that during validation we can run every validator, and once we have determined which ones failed with what error messages, and which ones passed, we then proceed to check whether the failed ones were meaningful. They are meaningful if their validation conditions evaluate to true. They could all be resolved at this phase (including the tricky @ValidationConditionOnValue).
In order to be more efficient though, we should attempt to only run validators whose validation conditions evaluate to true. This is very straightforward in the cases of @ValidationConditionOnTrue, @ValidationConditionOnUEL and @ValidationConditionOnEvent. For @ValidationConditionOnValue, which depends on other validators' success or failure, we can attempt to run the referenced validators lazily, unless we get into a dependency loop (which is valid: look at our previous example of requiring at least one of email or postal addresses), in which case we can start resolving the loop by running validators as described previously, since they are supposed to be without side-effect, and validation failures are only meaningful if they pass the validation condition later on.
Implementing @ValidationConditionOnEvent would required a pluggable validation condition from JPA, which could instruct us of the current operation on the bean (insert, update, delete) that triggered the validation. The ON_CHANGE condition event would simply fetch the value currently persisted and use Java equality to check for any change in the value.
JPA is set for another major revision through the JSR-317. We can only hope that some consistency will be included with Bean Validation (JSR-303) so that our gripes with unicity constraints as well as clear semantics for validation exceptions will be resolved.
1: I really wonder why the Annotation reification (reflection) of the annotation instance does not include pointers to the annotated object such as its type and value. Perhaps a question to ask the new annotation JSR project.
Stéphane Épardaud is a senior software developer at Lunatech Research. Although comments are disabled on this blog, he encourages you to send him comments by mail, corrections as well as opinions. Feedback is valued.
Please send comments on this article to editorial@lunatech.com.
Copyright © 2005-2012, Lunatech Research B.V. Tous droits réservés.