Help

This is the second part of a series of blogs about Bean Validation. For a general introduction, read this entry first. This part focuses on constraint definition.

While Bean Validation will come with a set of predefined basic constraints (like @NotNull, @Length and so on), a key feature of the specification is its extensibility. Application developers can and are strongly encouraged to write their own custom constraints matching a particular business requirement.

Writing a custom constraint

Because writing custom constraints is a core part of the specification goal, great care have been taken to make it as simple as possible. Let's walk through the process of creating a custom constraint.

As we have seen in the previous blog entry, a constraint is composed of

  • an annotation
  • an implementation

Constraint annotation

Every constraint is associated to an annotation. You can see it as a type safe alias and a descriptor. Constraint annotations can also hold one or several parameters that will help customize the behavior at declaration time

public class Order {
    @NotNull @OrderNumber private String number;
    @Range(min=0) private BigDecimal totalPrice;
    ...
}

Let's have a look at the @OrderNumber annotation definition

@Target({METHOD, FIELD})
@Retention(RUNTIME)
@ConstraintValidator(OrderNumberValidator.class)
public @interface OrderNumber {
    String message() default "{error.orderNumber}"; 
    String[] groups() default {}; 
}

Constraint annotations are just regular annotations with a few extra things:

  • they must use the runtime retention policy: the bean validation provider will inspect your objects at runtime
  • they must be annotated with @ConstraintValidator
  • they must have a message attribute
  • they must have a groups attribute

The @ConstraintValidator indicates to the bean validation provider that an annotation is a constraint annotation. It also points to the constraint validation implementation routine (we will describe that in a minute).

The message attribute (which generally is defaulted to a key) provides the ability for a constraint declaration to override the default message returned in the constraint error list. We will cover this particular topic in a later post.

groups lets a constraint declaration define the subset of constraints it participates to. Groups enable partial validation and ordered validation. We will cover this particular topic in a later post.

In addition to these mandatory attributes, an annotation can define any additional element to parameterize the constraint logic. The set of parameters is passed to the constraint implementation. For example, a @Range annotation needs min and max attributes.

@Target({METHOD, FIELD})
@Retention(RUNTIME)
@ConstraintValidator(RangeValidator.class)
public @interface Range {
        long max() default Long.MAX_VALUE;

        long min() default Long.MIN_VALUE;

        String message() default "{error.range}";
        String[] groups() default {}; 
}

Now that we have a way to express a constraint and its parameters, we need to provide the logic to validate the constraint.

Constraint implementation

The constraint implementation is associated to its annotation through the use of @ConstraintValidator. In the first early draft, @ValidatorClass is sometimes used in lieu of @ConstraintValidator: this is a mistake driven by a last minute change, sorry. The implementation must implement a very simple interface Constraint<A extends Annotation> where A is the targeted constraint annotation

public class OrderNumberValidator implements Constraint<OrderNumber> {
        public void initialize(OrderNumber constraintAnnotation) {
                //no initialization needed
        }

        /**
         * Order number are of the form Nnnn-nnn-nnn when n is a digit
         * The sum of each nnn numbers must be a multiple of 3
         */
        public boolean isValid(Object object) {
                if ( object == null) return true;
                if ( ! (object instanceof String) )
                        throw new IllegalArgumentException("@OrderNumber only applies to String");
                String orderNumber = (String) object;
                if ( orderNumber.length() != 12 ) return false;
                if ( orderNumber.charAt( 0 ) != 'N'
                                || orderNumber.charAt( 4 ) != '-'
                                || orderNumber.charAt( 8 ) != '-'
                                ) return false;
                try {
                        long result = Integer.parseInt( orderNumber.substring( 1, 4 ) )
                                        + Integer.parseInt( orderNumber.substring( 5, 8 ) )
                                        + Integer.parseInt( orderNumber.substring( 9, 12 ) );
                        return result % 3 == 0;
                }
                catch (NumberFormatException nfe) {
                        return false;
                }
        }
}

The initialize method receives the constraint annotation as a parameter. This method typically does:

  • prepare parameters for the isValid method
  • acquire external resources if needed

As you can see the interface entirely focuses on validation and leaves other concerns such as error rendering to the bean validation provider.

isValid is responsible for validating a value. A few interesting things can be noted:

  • isValid must support concurrent calls
  • exceptions should be raised when the object type received does not match the validation implementation expectations
  • the null value is not considered invalid: the specification recommends to split the core constraint validation from the not-null constraint validation and use @NotNull if the property must not be null

This easily customizable approach gives application programmers the freedom they need to express constraints and validate them.

Applying the same constraint type multiple times

Especially when using groups, you will sometimes need to apply the same type of constraint multiple times on the same element. The Bean Validation specification takes into account annotations containing an array of constraint annotations:

@Target({METHOD, FIELD})
@Retention(RUNTIME)
public @interface Patterns {
        Pattern[] value();
}

@ConstraintValidator(PatternValidator.class)
@Target({METHOD, FIELD})
@Retention(RUNTIME)
public @interface Pattern {
        /** regular expression */
        String regex();

        /** regular expression processing flags */
        int flags() default 0;

        String message() default "{validator.pattern}";
        
        String[] groups() default {};
}

In this example, you can apply multiple patterns on the same property

public class Engine {
        @Patterns( {
            @Pattern(regex = "^[A-Z0-9-]+$", message = "must contain alphabetical characters only"),
            @Pattern(regex = "^....-....-....$", message="must match ....-....-....")
                        } )
        private String serialNumber;
        ...

Building constraints

By default, Bean Validation providers instantiate constraint validation implementations using a no-arg constructor. However, the specification offers an extension point to delegate the instantiation process to a dependency management library such as Web Beans, Guice, Spring, JBoss Seam or even the JBoss Microcontainer.

Depending on the capacity of the dependency management tool, we expect validation implementations to be able to receive injected resources if needed: this mechanism will be entirely dependent on the dependency management tool.

Class-level constraints

Some of you have expressed concerns about the ability to apply a constraint spanning multiple properties, or to express constraint which depend on several properties. The classical example is address validation. Addresses have intricate rules:

  • a street name is somewhat standard and must certainly have a length limit
  • the zip code structure entirely depends on the country
  • the city can often be correlated to a zipcode and some error checking can be done (provided that a validation service is accessible)
  • because of these interdependencies a simple property level constraint does to fit the bill

The solution offered by the Bean Validation specification is two-fold:

  • it offers the ability to force a set of constraints to be applied before an other set of constraints through the use of groups and group sequences. This subject will be covered in the next blog entry
  • it allows to define class level constraints

Class level constraints are regular constraints (annotation / implementation duo) which apply on a class rather than a property. Said differently, class-level constraints receive the object instance (rather than the property value) in isValid.

@Address 
public class Address {
    @NotNull @Max(50) private String street1;
    @Max(50) private String street2;
    @Max(10) @NotNull private String zipCode;
    @Max(20) @NotNull String city;
    @NotNull private Country country;
    
    ...
}
@ConstraintValidator(MultiCountryAddressValidator.class)
@Target(TYPE)
@Retention(RUNTIME)
public @interface Address {
    String message() default "{error.address}";
    String[] groups() default {};
}
public class MultiCountryAddressValidator implements Constraint<Address> {
        public void initialize(Address constraintAnnotation) {
                //initialize the zipcode/city/country correlation service
        }

        /**
         * Validate zipcode and city depending on the country
         */
        public boolean isValid(Object object) {
                if ( ! (object instanceof Address) )
                        throw new IllegalArgumentException("@Address only applies to Address");
                Address address = (Address) object;
                Country country = address.getCountry();
                if ( country.getISO2() == "FR" ) {
                    //check address.getZipCode() structure for France (5 numbers)
                    //check zipcode and city correlation (calling an external service?)
                    return isValid;
                }
                else if ( country.getISO2() == "GR" ) {
                    //check address.getZipCode() structure for Greece
                    //no zipcode / city correlation available at the moment
                    return isValid;
                }
                ...
        }
}

The advanced address validation rules have been left out of the address object and implemented by MultiCountryAddressValidator. By accessing the object instance, class level constraints have a lot of flexibility and can validate multiple correlated properties. Note that ordering is left out of the equation here, we will come back to it in the next post.

The expert group has discussed various multiple properties support approaches: we think the class level constraint approach provides both enough simplicity and flexibility compared to other property level approaches involving dependencies. Your feedback is welcome.

Conclusion

Custom constraints are at the heart of JSR 303 Bean Validation flexibility. It should not be considered awkward to write a custom constraint:

  • the validation routine captures the exact validation semantic you expect
  • a carefully chosen annotation name will make constraints extremely readable in the code

Please let us know what you think here. You can download the full specification draft there. The next blog entry will cover groups, constraints subsets and validation ordering.

7 comments:
11. Apr 2008, 08:42 CET | Link

at first glance it seems to me that Constraint could benefit from being parameterized by the type of the value that it validates, such as:

public class Order {
  @OrderNumber private String number;
}
@Target({METHOD, FIELD})
@Retention(RUNTIME)
@ConstraintValidator(OrderNumberValidator.class)
public @interface OrderNumber {
  //...
}
public class OrderNumberValidator implements Constraint<OrderNumber, String> {
  public void initialize(OrderNumber constraintAnnotation) {}

  public boolean isValid(String orderNumber) {
    if ( orderNumber == null) return true;
    if ( orderNumber.length() != 12 ) return false;
    // ...
    return true;
  }
}

Unfortunateley, one can't restrict the @Target of an annotation by it's type (such as String for the @OrderNumber)...

ReplyQuote
11. Apr 2008, 20:19 CET | Link
Oli Yu

One big question around bean validators is how to deal with different messages within the validator. Even with the simple address validator above, it would be insufficient to return a single message for the whole bean validation.

In the Address example, for example, you may want to give different error instructions between the French zipcode (like saying it didn't have 5 numbers) and a US zipcode. And this even be distinct from an error message that while the format was correct, it didn't correlate to the city. For bean validators, it seems like you'd want to be able to pass multiple InvalidValues or InvalidConstraints back.

If the answer is to write a dozen, more granular validators (for example, one to validate the country, one to validate a French zipcode, one to validate the French city to the zipcode, etc.), then we may be unnecessarily increasing complexity of validations and duplicating code across validators.

Is there a better way to handle this?

12. Apr 2008, 15:59 CET | Link

I agree with the direction you are heading with this concern. Always having to deal with the null and type checks right at the beginning of the isValid method is annoying. Rather than generics, a better way might be to have the framework provide a helper routine to take care of this dirty work.

 
06. May 2008, 01:08 CET | Link

Hi!

I looks promising, but I still feel it is missing key functionality (maybe it can be done but is just not explained here):

What if I need to run a JPAQL query to find out if my object is valid (for example look in a zip-code table mapped by one of my POJO classes) how can I reach the EntityManager from inside the validator?

Not all validations need to be checked for the same operation, some should be checked for INSERT, some for UPDATE and some others for DELETE: How do I specify that want to validate, by running a query, before delete? (For example, you can not delete a Customer that still owes you some payments)

How can I generate detailed error messages for Class-level constraints, currently it seems I can only say it is valid or it not valid but it doesn't allow to expose contextual information explaining why it is not valid or if it was found to be invalid on INSERT, UPDATE or DELETE?.

Thanks

Regards,

07. May 2008, 23:37 CET | Link

Oli Yu, we solved this problem with our use of Hibernate Validators for postal code validation in the UK and Germany. We have a Country strategy design pattern class that includes country-specific validation rules. Instead of putting postal code validations directly on the postalCode field, we perform custom validations as:

@AssertTrue(message=address.postalCode.format)

Boolean isPostalCodeFormatValid() {return getCompany().isPostalCodeFormatValid(postalCode);}

@AssertTrue(message=address.postalCode.cityMatch)

Boolean doesPostalCodeMatchCity() {return getCompany().doesPostalCodeMatchCity(postalCode, city);}

This way, validation code is both modular and reusable from the Company class.

As you can see, we also rely on message keys, which we don't convert to true error messages till the errors hit the presentation tier.

One alternative solution is to create custom class validator constraints.

Hope this helps.

 
12. May 2008, 21:46 CET | Link

Hi,

Great job but...

can it go a little deeper - and handle unique constraint (and maybe uniques of multiple attributes) - lots of code is written only to check if e.g. login, project name, invoice number is available.

It is not so easy to get this information from database exception (even using spring) and even so it can be done only after other validation has already passed and we decided to make the create/update.

To check this without saving, service method is needed (like: getByXXX or isXXXAvialble()) - it could be handled with generics not to add much to business interfaces of services.One advantage of getByXXX is that we get potentially conflicting entity from DB and we may compare it so it solves differences in handling create and update actions - minuses we need to load the conflicting entity and support another way of object comparison (note that both '==' and equals does not fit all situations - we should rather compare PKs). isXXXAvailable - is much simpler but enough only for CREATE - with update there is no way to distinct if the value is used by 'our' entity or other one.

Should the bean validtion support it or at least suggest how to handle it in a mature way...

regards

Dominik

 
26. Jan 2010, 16:19 CET | Link

Hi, I have a blog-post on creating a reusable custom constraint annotation to validate constraints that spans multiple properties:

http://soadev.blogspot.com/2010/01/jsr-303-bean-validation.html

Comments are welcome. Cheers! pino

Post Comment