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).