Thursday, 14 July 2011

Misapplying JSR 303 Annotations

One of the main things to remember when applying constraint annotations to your beans is that not every annotation can be applied to every data type. This is fairly obvious when considering the @Future and @Past annotations as from their names you can deduce that they’re applicable to date / time values of some description.

But, what about the more generically named annotations? Take for example, @Size. This should be applied to the length of String, Collection, Map and array types validating their minimum and maximum lengths. It seems to me that an equally valid thing to do would be to apply it to an integer as you may think that the @Size annotation could refer to the number of digits in, or the maximum and minimum values of an integer. You could, for example, try the following:

  @Size(min = 1, max = 31)
private int startDay;

This will compile and deploy okay, but at runtime when the Hibernate Validator tries to validate your bean, you’ll get the following exception:
Jun 17, 2011 8:31:50 AM org.apache.catalina.core.StandardWrapperValve invoke
SEVERE: Servlet.service() for servlet [main-dispatcher] in context with path [/marin-tips-spring3-webapp] threw exception [Request processing failed; nested exception is javax.validation.UnexpectedTypeException: No validator could be found for type: java.lang.Integer] with root cause
javax.validation.UnexpectedTypeException: No validator could be found for type: java.lang.Integer
 at org.hibernate.validator.engine.ConstraintTree.verifyResolveWasUnique(
 at org.hibernate.validator.engine.ConstraintTree.findMatchingValidatorClass(
 at org.hibernate.validator.engine.ConstraintTree.getInitializedValidator(
 at org.hibernate.validator.engine.ConstraintTree.validateConstraints(
 at org.hibernate.validator.engine.ConstraintTree.validateConstraints(
 at org.hibernate.validator.metadata.MetaConstraint.validateConstraint(
 at org.hibernate.validator.engine.ValidatorImpl.validateConstraint(
 at org.hibernate.validator.engine.ValidatorImpl.validateConstraintsForDefaultGroup(
 at org.hibernate.validator.engine.ValidatorImpl.validateConstraintsForCurrentGroup(
 at org.hibernate.validator.engine.ValidatorImpl.validateInContext(
 at org.hibernate.validator.engine.ValidatorImpl.validate(

The down side of this misapplication is that you don’t find out what’s wrong until runtime although it does explain your problem.

It could be argued that the @Size constraint is badly named. Its name is not intuitive and implies that it can be used as constraint for any datatype. Perhaps given that @Size can only be applied to String, Collection, Map and arrays, then there should be a set of more relavent names: @StringSize, @CollectionSize, @MapSize etc.

The opposite mistake to the one outlined above would be to think that the @Min annotation could be used to constrain the length of a String; for example if you wanted the minimum length a String to be 1 you might write:

  @Min(value = 1)
private String eventType;

This time, you don’t get an exception thrown, but the constraint will always fail. You should use @Size in this case, but looking at the name, @Min it’s not obvious. Should the constraint name really reflect what it can be applied to? For example: @IntMin?

Some mis-applications are more obvious. It doesn’t make sense to test a primitive value for null as zero is a valid value.

  @NotNull(message = "Whoooops")
private int startDay;

The code above doesn’t throw any exceptions, but it should, so that you are notified that you’ve made a mistake.

Having said all this, the documentation does spell out which data types each of the annotations can be applied to, but in this day and age should you need to go back to that old acronym: RTFM? I guess that we should really hope for exceptions to be thrown consistently on the misapplication of constrain annotations and those exceptions should be more readable. I wait with baited breath.

No comments: