Coding Custom Constraints — Constraints For Primitive Fields

In previous section, we saw how to use the built-in Hibernate constraints, e.g. @Size and @Email. But, what about custom validation? For example, when signing up, how to check the uniqueness of an email id?

These custom validations can of course be done manually inside the service method, but that'd prevent us from showing all the errors at one go. In other words, if there'll be any bean validation error, our service code won't be executed, and the user won't know about the duplicate email. Only after he corrects the bean validation errors will our service code execute, and then he'll know about any custom errors.

To show all the errors on the first submit, we can code custom constraints, which behave quite similar to the built-in constraints.

Coding Custom Constraints

So, let's see how to code a custom constraint, say @UniqueEmail. We'll need to code two things basically, an annotation interface and a validator class.

The annotation interface

The annotation interface could look as below:

Notice that the interface is annotated with some other constraints, i.e. @NotBlank, @Size and @Email. So, when a field will be annotated with @UniqueEmail, those will also be applied to that. This is a good way to compose new constraints out of existing ones.

Notice also the @Constraint annotation above. It has a validatedBy attribute set to UniqueEmailValidator.class, which means that the validation rule for this constraint is coded in the UniqueEmailValidator class.

The message() attribute with default "{}" sets the default error message for this constraint as the message with key

The validator class

The UniqueEmailValidator class can be coded as below:

It implements ConstraintValidator with generic parameters UniqueEmail and String — the type of the annotation and the field to be validated.

The isValid method is the place containing the validation logic, which should return true if the field is valid, otherwise false.

Notice that it's annotated with @Component, so that it becomes a bean. It you omit it, Spring will anyway make it a bean the first time it's used, which will affect the first signup request after an application restart — although marginally.

Constraint composition

Mentioning a validator isn't mandatory when coding a constraint annotation. You can just have validatedBy = { }. See this for example:

It defines a @Password annotation which is nothing but a combination of the given @NotBlank and @Size.

These composed constraints help following the DRY principle — if you have password fields at several places in your forms, you can annotate all of those with just @Password, and in future, if the password format changes, you will have to make the change only at this place.

Okay, all this is about validating individual fields. But, how to do validations that require to refer to multiple fields? Join me in the next lesson to discuss that.