I'm very proud to report that today Hibernate Validator 4.1.0 Final has been released. Besides many bug fixes this release adds also a lot of new features to the code base.
The changes fall into four areas, which I'm going to discuss in detail in the following:
- New constraint annotations
- ResourceBundleLocator API
- API for programmatic constraint creation
- Constraint annotation processor
New constraint annotations
In addition to the constraints defined in the Bean Validation spec and those custom constraints already part of HV 4.0, the new release ships with the following new constraint annotations:
- @CreditCardNumber: Validates that a given String represents a valid credit card number using the Luhn algorithm. Useful to detect mis-entered numbers for instance.
- @NotBlank: Validates that a given String is neither null nor empty nor contains only whitespaces.
- @URL: Validates that a given String is a valid URL. Can be restricted to certain protocols if required:
@URL(protocol = "http") private String url;
- @ScriptAssert: Allows to use scripting or expression languages for the definition of class-level validation routines.
Let's have a closer look at the @ScriptAssert constraint, which I'm particularly excited about as I have implemented it :-).
The intention behind it is to provide a simplified way for expressing validation logic that is based on multiple attributes of a given type. Instead of having to implement dedicated class-level constraints the @ScriptAssert constraint allows to express such validation routines in an ad hoc manner using a wide range of scripting and expression languages.
In order to use this constraint an implementation of the Java Scripting API as defined by JSR 223 ("Scripting for the JavaTM Platform") must be part of the class path. This is automatically the case when running on Java 6. For older Java versions, the JSR 223 RI can be added manually to the class path.
As example let's consider a class representing calendar events. The start date of such an event shall always be earlier than the end date. Using JavaScript (for which an engine comes with Java 6) this requirement could be expressed as follows:
1 2 3 4 5 6 7 8 9 |
@ScriptAssert(lang = "javascript", script = "_this.startDate.before(_this.endDate)") public class CalendarEvent { private Date startDate; private Date endDate; //... } |
So all you have to do is to specify a scripting expression returning true or false within the script
attribute. The expression must be implemented in the language given within the language
attribute. The language name must be the language's name as registered with the JSR 223 ScriptEngineManager. Within the expression the annotated element can be accessed using the alias _this
by default.
The cool thing is that the @ScriptConsert constraint can be used with any other scripting language for which a JSR 223 binding exists. Let's take JEXL from the Apache Commons project for instance. Using Maven you only have to add the following dependency:
1 2 3 4 5 |
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-jexl</artifactId> <version>2.0.1</version> </dependency> |
With JEXL dates can be compared using the "<" operator. Using a shorter alias for the evaluated object the constraint from above therefore can be rewritten as follows:
1 2 3 4 5 6 7 8 9 |
@ScriptAssert(lang = "jexl", script = "_.startDate < _.endDate", alias = "_") public class CalendarEvent { private Date startDate; private Date endDate; //... } |
Very likely one will work with only one scripting language throughout all @ScriptAssert constraints of an application. So let's leverage the power of constraint composition to create a custom constraint which allows for an even more compact notation by setting the attributes lang
and alias
to fixed values:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@Target({ TYPE }) @Retention(RUNTIME) @Constraint(validatedBy = {}) @Documented @ScriptAssert(lang = "jexl", script = "", alias = "_") public @interface JexlAssert { String message() default "{org.hibernate.validator.constraints.ScriptAssert.message}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; @OverridesAttribute(constraint = ScriptAssert.class, name = "script") String value(); } |
Note how the script
attribute of the composing @ScriptAssert constraint is overridden using the @OverridesAttribute meta-annotation. Using this custom constraint the example finally reads as follows:
1 2 3 4 5 6 7 8 9 |
@JexlAssert("_.startDate < _.endDate") public class CalendarEvent { private Date startDate; private Date endDate; //... } |
As shown the @ScriptAssert constraint allows to define class-level constraints in a very compact way.
But there is also a price to pay. As scripting languages are used, compile-time type-safety is lost. When for instance the startDate
attribute is renamed the script expression would have to be adapted by hand. Also evaluation performance should be considered when validation logic is getting more complex.
So I recommend to try it out and choose what ever fits your needs best.
The ResourceBundleLocator API
The Bean Validation API defines the MessageInterpolator interface which allows to plug in custom strategies for message interpolation and resource bundle loading.
As it turned out, most users only want to customize the latter aspect (e.g. in order to load message bundles from a database) but would like to re-use the interpolation algorithm provided by Hibernate Validator.
Therefore Hibernate Validator 4.1 introduces the interface ResourceBundleLocator which is used by HV's default MessageInterpolator implementation ResourceBundleMessageInterpolator to do the actual resource bundle loading.
The interface defines only one method, in which implementations have to return the bundle for a given locale:
1 2 3 4 5 |
public interface ResourceBundleLocator { ResourceBundle getResourceBundle(Locale locale); } |
The ResourceBundleLocator to be used can be set when creating a ValidatorFactory:
1 2 3 4 5 6 7 |
ValidatorFactory validatorFactory = Validation .byProvider(HibernateValidator.class) .configure() .messageInterpolator( new ResourceBundleMessageInterpolator( new MyCustomResourceBundleLocator())) .buildValidatorFactory(); |
The default ResourceBundleLocator implementation used by Hibernate Validator is PlatformResourceBundleLocator which simply loads bundles using ResourceBundle.loadBundle(). Another implementation provided out of the box is AggregateResourceBundleLocator, which allows to retrieve message texts from multiple bundles by merging them into a single aggregate bundle. Let's look at an example:
1 2 3 4 5 6 7 8 9 10 11 |
HibernateValidatorConfiguration configuration = Validation .byProvider(HibernateValidator.class) .configure(); ValidatorFactory validatorFactory = configuration .messageInterpolator( new ResourceBundleMessageInterpolator( new AggregateResourceBundleLocator( Arrays.asList("foo", "bar"), configuration.getDefaultResourceBundleLocator()))) .buildValidatorFactory(); |
Here messages from bundles "foo" and "bar" could be used in constraints. In case the same key was contained in both bundles, the value from bundle "foo" would have precedence, as it comes first in the list. If a given key can't be found in any of the two bundles as fallback the default locator (which provides access to the ValidationMessages bundle as demanded by the JSR 303 spec) will be tried.
API for programmatic constraint creation
Using the Bean Validation API constraints can be declared using either annotations and/or XML descriptor files. Hibernate Validator 4.1 introduces a third approach by providing an API for programmatic constraint declaration.
This API can come in handy for instance in dynamic scenarios where constraints change upon runtime or for testing scenarios.
As example let's consider two classes, Customer
and Order
, from a web shop application for which the following constraints shall apply:
- each customer must have a name
- each order must have a customer
- each order must comprise at least one order line
With help of the programmatic constraint API these constraints can be declared as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
ConstraintMapping mapping = new ConstraintMapping(); mapping .type(Order.class) .property("customer", ElementType.FIELD) .constraint(NotNullDef.class) .property("orderLines", ElementType.FIELD) .constraint(SizeDef.class) .min(1) .message("An order must contain at least one order line") .type(Customer.class) .property("name", ElementType.FIELD) .constraint(NotNullDef.class); |
As the listing shows the API is designed in a fluent style with the class ConstraintMapping as entry point. Constraints are declared by chaining method calls which specify to which property of which type which constraints should be added.
The API provides constraint definition classes such as NotNullDef etc. for all built-in constraints, which allow to access their properties (min(), message() etc.) in a type-safe way. For custom constraints you could either provide your own constraint definition class or you could make use of GenericConstraintDef which allows to identify properties by name.
Having created a constraint mapping it has to be registered with the constraint validator factory. As the programmatic API is not part of the Bean Validation spec, we must explicitely specify Hibernate Validator as the BV provider to be used:
1 2 3 4 5 6 |
Validator validator = Validation
.byProvider(HibernateValidator.class)
.configure()
.addMapping(mapping)
.buildValidatorFactory()
.getValidator();
|
If you now use this validator to validate an Order
object which has no customer set a constraint violation will occur, just as if the "customer" property was annotated with @NotNull.
Constraint annotation processor
The Hibernate Validator annotation processor might become your new favourite tool if you find yourself accidentally doing things like
- annotating Strings with @Min to specify a minimum length (instead of using @Size)
- annotating the setter of a JavaBean property (instead of the getter method)
- annotating static fields/methods with constraint annotations (which is not supported)
Normally you would notice such mistakes only during run-time. The annotation processor helps in saving your valuable time by detecting these and similar errors already upon compile-time by plugging into the build process and raising compilation errors whenever constraint annotations are incorrectly used.
The processor can be used in basically every build environment (plain javac, Apache Ant, Apache Maven) as well as within all common IDEs. Just be sure to use JDK 6 or later, as the processor is based on the "Pluggable Annotation Processing API" defined by JSR 269 which was introduced with Java 6.
The HV reference guide describes in detail how to integrate the processor into the different environments, so I'll spare you the details here. As example the following screenshot shows some compilation errors raised by the processor within the Eclipse IDE (click to enlarge):
Just for the record it should be noted that the annotation processor already works pretty well but is currently still under development and is considered as an experimental feature as of HV 4.1. If you are facing any problems please report them in JIRA. Some known issues are also discussed in the reference guide.
Summary
The focus for Hibernate Validator 4.0 was to provide a feature-complete, production-ready reference implementation of the Bean Validation spec.
While strictly staying spec-compliant, HV 4.1 goes beyond what is defined in JSR 303 and aims at generating even more user value by providing new constraints, new API functionality as well as an annotation processor for compile-time annotation checking.
In order to try out the new features yourself just download the release from SourceForge. Of course HV 4.1 can also be retrieved from the JBoss Maven repository.
If you are already using HV 4.0.x, the new release generally should work as drop-in replacement. The only exception is that we had to move the class ResourceBundleMessageInterpolator to the new package messageinterpolation
. So if you're directly referencing this class, you'll have to update your imports here.
The rationale behind this relocation was to clearly separate those packages which can safely be accessed by HV users from packages intended for internal use only. The public packages are:
org.hibernate.validator
org.hibernate.validator.cfg
org.hibernate.validator.constraints
org.hibernate.validator.messageinterpolation
org.hibernate.validator.resourceloading
Of course we'd be very happy on any feedback. Questions and comments can be posted in the HV forum, while any issues or feature requests should be reported in JIRA.