Validating Domain Objects in Hibernate Part 4: @NotNull and @NotEmpty

Posted on October 05, 2007 by Scott Leberknight

This is the fourth 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 third article I showed how to create your own validators. In this article I'll explain the statement I made in the last article that I don't use the @NotNull and @NotEmpty validations in practice, even though at first glance they would seem to be some very useful validators.

First the @NotEmpty validator. Actually this annotation is fine assuming you want to validate "that a String is not empty (not null and length > 0) or that a Collection (or array) is not empty (not null and length > 0)." That is the description in the JavaDoc for the @NotEmpty validator. My only problem with this is that @NotEmpty only applies to strings and collections or arrays. There are lots of times when you want to ensure that dates, numbers, or custom types are required, and @NotEmpty can't help you out. That's pretty much why I don't use it.

Now on to the @NotNull validation annotation. There is a major problem with this validator, which is that it simply doesn't behave the way other validators behave. If you try to save an object having a property annotated with @NotNull, and that property's value is actually null, you would expect to receive an InvalidStateException, which is what happens with other validators. What you actually receive is a PropertyValueException which is the result of Hibernate enforcing a nullability check on the property annotated with @NotNull. I have gone through what happens line-by-line in a debugger and, other than the fact that it is extremely complicated, eventually you arrive at the checkNullability() method in the Nullability class which checks "nullability of the class persister properties" according to the JavaDocs and throws a PropertyValueException with the message "not-null property references a null or transient value." This behavior happens even if the actual column in the database allows nulls!

For example, I have a simple User entity with an active property annotated with @NotNull defined as follows:

 @Type(type = "yes_no")
 @NotNull
 public Boolean getActive() {
     return active;
 }

The user table is defined like this (to show that the active column allows null values):

mysql> desc user;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | bigint(20)   | NO   | PRI | NULL    | auto_increment | 
| version    | bigint(20)   | YES  |     | NULL    |                | 
| active     | char(1)      | YES  |     | NULL    |                | 
| first_name | varchar(255) | YES  |     | NULL    |                | 
| last_name  | varchar(255) | YES  |     | NULL    |                | 
| user_name  | varchar(255) | YES  |     | NULL    |                | 
+------------+--------------+------+-----+---------+----------------+
6 rows in set (0.02 sec)

Finally, I have a test that shows that a PropertyValueException is thrown instead of an InvalidStateException:

@Test(expected = PropertyValueException.class)
public void testNotNullAnnotationPreemptsNormalValidation() {
    // Explicitly set property annotated with @NotNull to null
    user.setActive(null);
    session.save(user);
}

This test passes, meaning that you get a PropertyValueException where with other validators you get an InvalidStateException. For example, here is another test that tests the validation on the username property which is annotated with @Email:

@Test(expected = InvalidStateException.class)
public void testNormalValidationErrorIfNotNullPropertyIsValid() {
    //  Active property is OK here as it gets the default value 'true' in the User class

    // But...make username invalid
    user.setUserName("bob");
    session.save(user);
}

The above test passes meaning that an InvalidStateException was thrown. So, the point of all this long-windedness is that @NotNull behaves differently than other validators and results in a completely different exception being thrown. That is the reason I don't use it and why I created the @Required annotation I showed in the last article.

In the next article, I'll show a technique to bypass validation to allow objects to be saved without the normal validation occurring (and explain a use case where bypassing validation makes sense).



Post a Comment:
Comments are closed for this entry.