Chapter 4. Advanced Topics

Table of Contents

4.1. Customized Error Messages
4.2. JavaBeans property access
4.2.1. Inheritence
4.2.2. Property access
4.3. Extending the Bean Validator
4.3.1. Defining your own validators
4.3.2. Defining your own validation vetoers

This chapter covers advanced topics that you may need to know when polishing your application, tuning peformance, or extending the Bean Validator framework.

4.1. Customized Error Messages

The Bean Validator ships with an example English-language BeanValidator.properties resource file that provides default error messages for the built-in validators.

The resource file is used a a stock PropertyResourceBundle, and the messages are formatted using MessageFormat. Please consult the JavaDocs for the com.sadalbari.validator.core.validators package for detailed specifications on the parameters emitted by each validator.

4.2. JavaBeans property access

We've tried to make the Bean Validator as flexible as possible with respect to where annotations may be placed. Behind this flexibility lies complicated use of the Java Reflection API. Whatever anybody says, this will never be as performant as direct calls, so this section highlights some of the inner workings of the framework for you to bear in mind.

4.2.1. Inheritence

Inheritence is implicitly supported by the Bean Validator. Annotations are collected from superclasses and interfaces. Overridden methods will completely override their superclass' annotations.

If a comparative validator or vetoer references another property, that property will first be reflected in the same class. Failing that, the framework will work its way up the hierarchy.

4.2.2. Property access

The default access strategy is to use property access, i.e. access via appropriately (as per the JavaBeans conventions) named getters. This may be overridden using the Validated annotation.

If you do choose to use field-level access, it must be done consistently in the class hierarchy.

4.3. Extending the Bean Validator

Despite the comprehensive array of property validations and vetoers supplied, almost every application produces specialized requirements. Extending the framework is quite easy.

4.3.1. Defining your own validators

The first step is to define an annotation. Let's say we're defining an ISBN validator:

@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@PropertyValidationClass(ISBNValidator.class)
public @interface ISBN {
    
    String message() default "{myvalidator.isbn}";
    
    String[] groups() default "";
    
}

Both of these methods are required.

Now let's define the implementation class:

public class ISBNValidator implements PropertyValidator<ISBN> {

    public ISBNValidator() {
        super();
    }
    
    public PropertyValidationError[] validate(ValidationContext context,
            Member annotatedMember, ISBN annotation, Object target, 
            Object value) {
        // Do some work and add PropertyValidationErrors if necessary...
    }
}

Let's assume that the ISBNValidator class produces as its only message argument the property name. Now let's add the following line to our default BeanValidator.properties:

myvalidator.isbn = {0} must be a valid ISBN string

4.3.2. Defining your own validation vetoers

Once again, let's start with the annotation. This example will only run validation if another property is even.

@PropertyValidationVetoClass(IfEvenVetoer.class)
@Documented
@Retention(RUNTIME)
@Target({FIELD, METHOD})
public @interface IfEven {
    
    String value();
    
    String[] groups() default "";
    
}

Here, the value attribute is the other property's name. The vetoer implementation is shown next.

public class IfEvenVetoer implements PropertyValidationVetoer<IfEven> {

    public IfEvenVetoer() {
        super();
    }
    
    public boolean veto(ValidationContext context, Member annotatedMember,
            IfEven annotation, Object target, Object value) {
        
        final Number otherValue = // Reflect annotation.value() somehow
        return !(otherValue.intValue % 2 == 0);
        
    }

}