Cross Field Validation in Vaadin

The core Vaadin infamously has no support for cross field validation. It is our second most voted feature among pro support users. The reason why it hasn’t been done is that it is really hard to implement perfectly in Vaadin, mostly for the reason that Vaadin contains “UI field level buffering” as a feature. But, if you have been following my thoughts on Vaadin development, you should never use that feature and we should have never (over ten years ago) added that feature.

The workaround for the missing feature has been to use completely custom logic, before you pass your DTO to your backend. With this approach you can do pretty much anything at all, but it makes your code smell and it is hard to hook it properly with “eager validation” — something I want for all my applications.

The feature has just been postponed for too long, so I decided prototype the feature in Viritin add-on. The first version is available in Viritin 1.34. Naturally the UI level buffering is not supported 🙂 , but otherwise I think the feature is already pretty handy. This is a brief introduction to what is now supported and what could be improved.

Generic stuff about the implementation

The implementation is mostly written into MBeanFieldGroup, an extension to BeanFieldGroup from core, which already e.g. supports eager field validation. More commonly you might be using it via AbstractForm (a helper class in Viritin) that behind the scenes uses MBeanFieldGroup for databinding. MBeanFieldGroup also contains shorthand methods for related API.

An essential design decision I did was that cross field validation is only executed once all field level validators, handled by core Vaadin, are passed. Although the field level validation in Vaadin core works in “fail-fast” manner, I decided to execute all bean level validations once the execution gets that far. This may cause some odd situations for end users as the number of validation errors might suddenly increase, but this approach felt most natural now and it was the easiest to implement with JSR 303 style bean level validation API.

Programmatic validation

Using JSR 303 (aka Bean Validation) style declarative (annotation) approach is pretty handy for generic field level validation. But using it at bean level is not necessary “pretty”. Bean level (typically cross field) annotations are pretty much always custom tailored. Defining the actual validator and annotations declaring it requires quite a lot of code and it becomes rather complex to use them.

Thus I started my implementation with a simple programmatic approach. The MValidator is pretty similar to the core Vaadin Validator interface but with a bit better typing. It is pretty easy to write the bean level validator instance inline (with e.g. Java 8 lambdas) or split them to reusable classes. My test validator looks like this:

public class MValidatorImpl implements
MBeanFieldGroup.MValidator<Reservation> {

@Override
public void validate(Reservation value) throws Validator.InvalidValueException {
// No null checks needed as this is not reached unless
// field validators pass (which has @NotNull in this case
if (value.getStart().getTime() > value.getEnd().getTime()) {
throw new Validator.InvalidValueException(
"End time cannot be before start time!");
}
}
}

You can add them to either AbstractForm implementations or directly to the MBeanFieldGroup. AbstractForm will not let you click save and MBeanFieldGroup will report it as invalid, if there are issues either with either field level or top level validators.

JSR 303 support!

JSR 303 validators are naturally added directly with annotations to your beans. For my test I googled a generic “field match example” with both the annotation and the validator implementation. AbstractForm or MFieldGroup will automatically take that kind of JSR 303 bean level annotations into account.

Displaying constraint violations

Displaying the actual errors is something I’m bit uncertain at this point. I don’t know exactly how this should be typically tackled, so I made the initial API pretty open. For programmatic validators you can pass also the property names on which the validation error should be displayed, which works well in some cases. For both programmatic and JSR303 annotation style validators you can also define the “error target component” with setValidationErrorTarget(Class, Component).

But as some top level/ cross field validations don’t relate strictly to some fields, I added some mechanisms for developer to detect the validation errors. Constraint violations reported by both programmatic and declarative approaches are available via MBeanFieldGroup.getBeanLevelValidationErrors(), which returns a collection of strings. Strings are JSR 303 validation messages or the messages reported by the InvalidValueException(s). The raw ConstraintViolation reports raised by JSR303 validators are also available via MBeanFieldGroup.

AbstractForm also contains an optional rudimentary mechanism to show these unbound validation errors at form level. You can use getConstraintViolationsDisplay() method to get a component that displays the currently reported top level validation errors.

Possible improvement possibilities

The solution is so far tested only in the one integration test in Viritin and an other private project, so it might still need rather big enhancements to be generic solution. I hope you can try this out and if you find find some issues with the solution, please let me know about them soon so we could improve the API. It is also good info to know that the solution suits for your use case.

To the master branch in github, I have already improved the JSR 303 usage by adding a groups support, but so far it is not that useful as groups are not supports at field level.

I hope you find this useful and we can solve this soon in the core as well!

Cross field validation integration test in Viritin

Like what you read? Give Matti Tahvonen a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.