Bean Validation and Vaadin comprehensive example

Submitted on 22 March 2012

One of Vaadin 7 key features is its tight JSR 303, also known as Bean Validation, integration. This article will detail on how to achieve such a validation.

For starters, let’s have a simple use-case: the user fills in a form. When there’s an error, the user is informed visually about the error. Submitting the form is only possible when all errors have been corrected. This a common scenario and we’ll implement it with Vaadin.

Prerequisites

There are a couple of prerequisites to address before going further:

  1. First, we’ll need a JSR 303 implementation. In this case, we’ll use Hibernate Validator (which is also JSR-303 Reference Implementation)
  2. The next step is to define the underlying bean. In our case, we’ll keep it simple (and stupid): a Person and its associated attributes. The first name and the last name must not be null while the email, when it exists, has to respect an email pattern.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.util.Date;
 
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
 
import org.hibernate.validator.constraints.Email;
 
public class Person {
 
    private long id;
 
    private Gender gender;
 
    @Size(max = 250)
    @NotNull
    private String firstName;
 
    @Size(max = 250)
    @NotNull
    private String lastName;
 
    @Size(max = 250)
    @Email
    private String email;
 
    private Date birthdate;
 
    // Getters and setters
}

Note that NotNull and Size constraints come from the JSR itself (javax.validation.constraints package) while Email comes from Hibernate Validator (org.hibernate.validator.constraints package). Despite the coupling, considering the added value, I don’t have any scruples using it.

Validating

Once the bean is annotated, it’s only a matter of:

  • Creating a new bean: this is pretty self-explanatory
BeanItem<Person> item = new BeanItem<Person>(new Person());
  • Vaadin 7 having deprecated the Form class, we need to put the bean into its replacement, FieldGroup.
FieldGroup group = new FieldGroup(item);
  • Then, we need to get a reference on each displayable field (as opposed to Form where there was a default and we could customize it). The FieldGroup has methods to create each field and to bind it to the underlying bean property.
Field<?> gender = group.buildAndBind("Gender", "gender", Select.class);
Field<?> firstName = group.buildAndBind("First name", "firstName");
Field<?> lastName = group.buildAndBind("Last name", "lastName");
Field<?> email = group.buildAndBind("email");
Field<?> birthdate = group.buildAndBind("Birth date", "birthdate");
  • Each field is then set its validator (if needed)
firstName.addValidator(new BeanValidator(Person.class, "firstName"));
lastName.addValidator(new BeanValidator(Person.class, "lastName"));
email.addValidator(new BeanValidator(Person.class, "email"));
  • Finally, each field has to be set on our layout the way we want
layout.addComponent(gender);
layout.addComponent(firstName);
layout.addComponent(lastName);
layout.addComponent(email);
layout.addComponent(birthdate);

Finishing touches

There are a couple of finishing touches necessary to achieve a polished user interface.

No preliminary validation

If all previous code snippets take place in the init() method, users will see validation errors on first and last names when the page is displayed, since all bean attributes are initially empty.

Thus, it’s higly desirable to install validators only after the page has been displayed for the first time without errors. This could be done when the field loses focus and yet, it has to be done even if the field never receives focus. Thus, we have to set it in two places: + In each validated field blur listener

firstName.addListener(new InstallPersonValidatorBlurListener(firstName, "firstName"));
lastName.addListener(new InstallPersonValidatorBlurListener(lastName, "lastName"));
email.addListener(new InstallPersonValidatorBlurListener(email, "email"));
 
public class InstallPersonValidatorBlurListener implements BlurListener {
 
    private Field<?> field;
    private String attribute;
 
    public InstallPersonValidatorBlurListener(Field<?> field, String attribute) {
 
        this.field = field;
        this.attribute = attribute;
    }
 
    @Override
    public void blur(BlurEvent event) {
 
        ValidatorUtils.installSingleValidator(field, attribute);
    }
}
  • Just before the FieldGroup commit
ValidatorUtils.installSingleValidator(firstName, "firstName");
ValidatorUtils.installSingleValidator(lastName, "lastName");
ValidatorUtils.installSingleValidator(email, "email");
 
group.commit();

Note that since we don’t keep a reference to the installed validator, we have to uninstall it before installing it otherwise it leads to twice (or more) the same validation.

For clarity’s sake, here’s the ValidatorUtils code:

public class ValidatorUtils {
 
    private ValidatorUtils() {}
     
    static void installSingleValidator(Field<?> field, String attribute) {
         
        Collection<Validator> validators = field.getValidators();
 
        if (validators == null || validators.isEmpty()) {
 
            field.addValidator(new BeanValidator(Person.class, attribute));
        }
    }
}

The right widget for the right data type

This point has nothing to do with validation, but it brings a nice finishing touch to the user interface. Did you notice that when we built the gender field, we passed a Select parameter and presto, the return field was a select. Wouldn’t it be better if the birthdate field would be displayed as a true DateField? We can try this:

Field<?> birthdate = group.buildAndBind("Birth date", "birthdate", DateField.class);

Unfortunately, this doesn’t work as Vaadin loudly complains with a com.vaadin.data.fieldgroup.FieldGroup$BindException: Unable to build a field of type com.vaadin.ui.DateField for editing java.util.Date. This means that in contrast to the old Form, FieldGroup doesn’t handle date fields. As both use field factories (albeit of a different type), this is easily corrected. We just have to create a field group factory that return DateField when passed Date attributes and delegates to the default field group factory otherwise.

public class EnhancedFieldGroupFieldFactory implements FieldGroupFieldFactory {
 
    private FieldGroupFieldFactory fieldFactory = new DefaultFieldGroupFieldFactory();
 
    @SuppressWarnings({ "rawtypes", "unchecked" })
    @Override
    public <T extends Field> T createField(Class<?> dataType, Class<T> fieldType) {
 
        if (Date.class.isAssignableFrom(dataType)) {
 
            return (T) createDateField();
        }
 
        return fieldFactory.createField(dataType, fieldType);
    }
 
    @SuppressWarnings({ "rawtypes", "unchecked" })
    protected <T extends Field> T createDateField() {
 
        DateField field = new DateField();
 
        field.setImmediate(true);
 
        return (T) field;
    }
}

Then, we set an instance of this factory to the field group, passing the field type we need.

Field<?> birthdate = group.buildAndBind("Birth date", "birthdate", DateField.class);

Remove initial ugly null values

Finally, since the Person bean is initialized with null values, null appears in the fileds when the page first loads. For a end-user, this is not particularly desirable. We have to cast the field returned by the field group to a more friendly type and call the setNullRepresentation() method.

AbstractTextField firstName = (AbstractTextField) group.buildAndBind("First name", "firstName");
 
firstName.setNullRepresentation("");

Conclusion

In this article, we used the power of Vaadin 7 to easily create a submit form, complete with validation coming from JSR 303 annotations. The full sources of this article can be found on Github.