Sunday, February 1, 2009

In my last post I explained how to get the JSR 303 reference implementation up and running and showed some of the very basic features of the Bean Validation API.

Today I want to delve a bit more into the details of the JSR by showing how to create custom constraint annotations and how to make use of the concept of validation groups.

Creating custom constraint annotations

Though the JSR defines a whole bunch of standard constraint annotations such as @NotNull, @Size, @Min or @AssertTrue (btw. raise your voice in the JSR feedback forum, if you want to have more standard annotations included), there will always be validation requirements, for which these standard annotations won't suffice.

Being aware of that problem, the specification authors laid out the API in an expansible manner, that allows users to define their custom constraint annotations.

To try that out, lets create a constraint annotation, that ensures, that the licensePlate field of the Car entity from my previous post always contains an upper-case String. First we have to define the annotation itself. If you've never designed an annotation before, this may look a bit scary, but actually it's not very hard:

An annotation type is defined using the @interface keyword. All attributes of an annotation type are declared in a method-like manner. The specification of the Bean Validation API demands, that any constraint annotation defines

an attribute "message" that returns the default key for creating error messages in case the constraint is violated

an attribute "groups" that allows the specification of validation groups, to which this constraint belongs (see section "Using validation groups" for further details). This must default to an empty array.

In addition we annotate the annotation type with a couple of so-called meta annotations:

The ConstraintValidator interface specifies two type parameters, which we set in our implementation. The first specifies the annotation type to be validated by a ConstraintValidator (in our example UpperCase), the second the type of elements, which the validator can handle (here String).

The implementation of the validator is straightforward. The initialize() method gives us access to any attributes of the annotation (such as the min/max fields in case of the Size annotatation), but as @UpperCase doesn't define any attributes, we have nothing to do here.

What's interesting for us, is the isValid() method. On line 20 we implement the logic, that determines, whether a given object is valid according to the @UpperCase annotation or not.

Note, that the specification recommends, that null values should be declared to be valid. If null is not a valid value for an element, it should be annotated with @NotNull explicitely. We could use the passed-in ConstraintValidatorContext to raise any custom validation errors, but as we are fine with the default behavior, we can ignore that parameter.

Specifying the error message

Finally we need to specify the error message, that shall be used, in case the @UpperCase constraint is violated. To do so, we create a file named ValidationMessages.properties under src/main/resources with the following content:

1

validator.uppercase=String must be upper-case.

If a validation error occurs, the validation runtime will use the default value, that we specified for the message attribute of the @UpperCase annotation to look up the error message in this file.

Now that our first custom constraint is completed, we can use it in the Car class to specify that the licensePlate field shall only contain upper case Strings:

Compound constraints

Looking at the license plate field of the Car class, we see three constraint annotations already. In complexer scenarios, where even more constraints could be applied to one element, this might become a bit confusing easily.

Furthermore, if we had a licensePlate field in another class, we would have to copy all constraint declarations to the other class as well, violating the DRY principle.

Luckily, the Bean Validation API comes to the rescue by allowing the creation of compound constraints. So let's compose a new constraint annotation @ValidLicensePlate, that comprises the constraints @NotNull, @Size and @UpperCase:

To do so, we just annotate the constraint declaration with its comprising constraints (btw. that's exactly why we allowed annotation types as target for the @UpperCase annotation). The validator class for @ValidLicensePlate doesn't add any additional validation logic, so we skip it for now.

Using the new compound constraint at the licensePlate field now is fully equivalent to the previous version, where we declared the three constraints directly at the field itself:

Using validation groups

Now, let's imagine another custom constraint annotation, @NotStolen, which could be annotated at the licensePlate field, too. Upon the validation of @NotStolen an external service shall be called, that checks whether a given license plate is stolen or not.

Calling this service would be rather expensive (in terms of processing time, but possibly also in terms of money, as we might be charged for each call by the service provider), so we are interested in calling the service not more than neccessary.

Especially we don't want the service to be called, if a Car instance is not even valid with respect to the @NotNull constraint at the manufacturer field or the @ValidLicensePlate constraint, e.g. due to an input form not completely filled.

To meet this requirement, we can leverage the concept of validation groups. Each constraint at the fields of an object to be validated can be part of one or more such validation group, and instead of validating all constraints at once, we validate one group after the other, stopping, if one group couldn't be validated successfully.

The @NotStolen constraint is part of the Extended validation group. On the contrary, the @NotNull and the @ValidLicensePlate constraints belong to the Default group (which is specified by the JSR 303 API), as no special validation group is specified for them.

We can use the validation group concept now in a sample class, that checks the validity of a given Car instance:

At first, only the constraints of the Default group are validated. If they are violated, processing stops here. Only if all constraints of the Default group are met, the Extended group is validated, issuing the expensive external service call required for the validation of the @NotStolen constraint.

Conclusion

That concludes my exploration of the JSR 303 (Bean Validation) API for now. I showed how to create custom constraint annotations, how to make use of constraint composition and finally how to leverage the concept of validation groups.

Of course that's not all to be said about the Bean Validation API. Features that I didn't mention during my journey through the JSR's API include class-level constraints, the proposed validation of method parameters and return values and the external declaration of constraints by the means of an XML descriptor.

JSR 303 remains in Public Review status for another couple of days. So you still have the chance to give feedback to the specification's authors. In my opinion the JSR looks really promising and I hope to see it soon in Final status, neatly integrated with the JSF 2.0 and JPA 2.0 APIs.

JSR 303 doesn't make any restrictions, where the BV API might be used:

"The validation API developed by this JSR is not intended for use in any one tier or programming model. It is specifically not tied to either the web tier or the persistence tier, and is available for both server-side application programming, as well as rich client Swing application developers."

That said, in the specification appendix integration possibilities with JPA (object validation might occur before inserting/updating records in(to) the DB) and JSF are considered.

@JodaStephen

This looks interesting. Are their any plans to support the JSR 303 API with OVal?

I sorry this is the first time to use JSR bean validation. i just need evaluate some of my study. i am quite confused where to save the Custom Constraint file (the interface) and the other validator class. could you please mention the names of the files and where to be stored..

Mustafa, you can basically name these classes/files as you like. They must be stored in the source directory of your project, e.g. somewhere under src/main/java in case of Maven project. You might also find the Hibernate Validator reference guide helpful.

I have a method like this, public void testNotNull(@NotNull(message = "name is mandatory") String name) { System.out.println(name); } how to check this one? name is not class field, it is just an function argument. Could you please let me know?

About Me

I'm an enthusiastic software engineer and architect interested in software engineering in general and in the Java ecosystem in particular.

I'm part of the Hibernate Team at Red Hat, working on Hibernate Validator, Search and OGM. I'm also member of the Bean Validation 1.1 (JSR 349) expert group. In my spare time I'm lead of the MapStruct project, a code generator for Java bean mappings.

The views expressed in this blog are my own and not those of my employer.