- Key Takeaways
- The Contextual Nature of Domain Model Validation
- Introducing Java Bean Validation with JSR 303
- Defining a Basic Domain Model
- Validating Constraints
- Encapsulating Validation Logic in a Service Component
- Creating Custom Validation Constraints and Validators
- Conclusions
- Frequently Asked Questions (FAQs) on Effective Domain Model Validation with Hibernate Validator
Let’s face it, domain model validation has always been a pretty untamable beast (and most likely this won’t change anytime soon) because the validation process itself is strongly bound to the context where it takes place. It’s feasible, however, to encapsulate the mechanics of validation by using some external class libraries, rather than messing up our lives doing it ourselves from scratch. This is exactly where Hibernate Validator comes into play, the reference implementation of Java Beans Validation 1.0 / 1.1 (JSR 303), a robust validation model based on annotations that ships with Java EE 6 and higher.
This article will walk you through using Hibernate Validator in a pragmatic way.
Key Takeaways
- Hibernate Validator, the reference implementation of Java Beans Validation 1.0 / 1.1 (JSR 303), is a robust validation model based on annotations that assists in the process of domain model validation.
- Java Beans Validation enables selective validation of class fields and even entire classes by using constraints declared with intuitive annotations.
- Hibernate Validator works by scanning annotations, checking the values assigned to the constrained fields, and returning the validation errors or constraint violations.
- It’s recommended to encapsulate Hibernate Validator within a decoupled service component, to avoid duplication of code and ensure adherence to the DRY (Don’t Repeat Yourself) principle.
- Hibernate Validator allows for the creation of custom validation constraints and validators, extending its core functionality to meet specific validation requirements.
The Contextual Nature of Domain Model Validation
Independent of your language of choice, building a rich domain model is one of the most challenging tasks you can tackle as a developer. And on top of that you’ll have to make sure that the data that hydrates the model is valid, thus assuring that its integrity is properly maintained. Unfortunately, in many cases making domain objects valid in the context of the containing application is far from trivial, due to the intrinsic contextual nature of the validation process per se.
To put it in another way: If the arguments taken by a domain object are supplied by the application’s environment (for instance by using plain Dependency Injection, Factories, Builders and so on) rather than by an external upper tier, then validating the object should be straightforward and constrained to a very limited scope.
Conversely, if the arguments in question are injected from an outer layer (a user interface layer is a good example of this), the validation process can be cumbersome and tedious, in most cases leading to having chunks of boilerplate code scattered across multiple application layers.
In the end, everything boils down to this simple yet fundamental question: What makes a domain object valid? Should its state be validated before being persisted or updated in a database, or when passed around to other layer(s)? The logical answer is: It depends. Remember that validation is always contextual! So no matter what approach you use to decide whether your domain objects are valid, Java Beans Validation will make the process easier.
Introducing Java Bean Validation with JSR 303
Prior to Java EE 6, Java didn’t provide a standard way of validating the fields of a domain class by mean of a centralized mechanism. But things have changed for the better since then. The Java Beans Validation specification makes it fairly easy to selectively validate class fields (and even entire classes) by using constraints declared with a few intuitive annotations.
At the time of this writing, JSR 303 has only two compliant implementations that you can pick up out there, Apache BVal and Hibernate Validator. The latter is the reference implementation, which can be consumed as a standalone library, completely decoupled from the popular ORM framework.
With that said, let’s see now how to get started using the Java Beans Validation / Hibernate Validator tandem for performing effective domain model validation in the real world.
Defining a Basic Domain Model
As usual, a nice way to show how to utilize Java Beans Validation for validating a domain model is with a concrete example. Considering that the standard implements an annotation-based validation schema, the classes that are part of the domain model must always be constrained with annotations.
In this case, for clarity’s sake, the domain model I want to validate will be composed of only one naive, anemic class, which will be the blueprint for user objects:
public class User {
private int id;
@NotEmpty(message = "Name is mandatory")
@Size(min = 2, max = 32,
message = "Name must be between 2 and 32 characters long")
private String name;
@NotEmpty(message = "Email is mandatory")
@Email(message = "Email must be a well-formed address")
private String email;
@NotEmpty(message = "Biography is mandatory")
@Size(min = 10, max = 140,
message = "Biography must be between 10 and 140 characters long")
private String biography;
public User(String name, String email, String biography) {
this.name = name;
this.email = email;
this.biography = biography;
}
// Setters and Getters for name, email and biography
}
There is nothing worth discussing except for the constraints declared on top of each field. For instance, the @NotEmpty(message = "Name is mandatory")
annotation states that the name
field must be, yes, a non-empty string. Even though it’s pretty self-explanatory, the message
attribute is used for defining the message that will be displayed if the constrained field raises a violation when being validated. Even better it’s possible to customize many constraints with parameters in order to express more refined criteria. Consider the @Size(min = 2, max = 32, message = "...")
annotation. It expresses, well, exactly what the message says. Not rocket science, right?
The specification provides a few more, handy annotations. For the full list, feel free to check them here.
Validating Constraints
At this point, we’ve managed to define a constrained model class by using Java Beans Validation, which is all well and fine. But you might be wondering what are the practical benefits of doing this? The laconic answer is: none – so far. The class is ready to be validated, sure, but the missing piece here is having a mechanism, capable of scanning the annotations, checking the values assigned to the constrained fields, and returning the validation errors (or in JSR 303 terminology, the constraint violations). And that’s exactly how Hibernate Validator works under the hood.
But let’s be frank: The above explanation would be just technical gibberish if we don’t see at least a contrived example that shows how to use Hibernate Validator for validating the User
class:
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
User user = new User("John Doe", "no-mail", "");
validator
.validate(user).stream()
.forEach(violation -> System.out.println(violation.getMessage()));
That was really easy, wasn’t it? The snippet first grabs an instance of Hibernate Validator through the static buildDefaultValidatorFactory()
and getValidator()
methods that are part of the Bean Validation API, and uses the validate()
method for validating a potentially invalid user object. In this case, the constraint violations are displayed in the console by using streams and lambdas altogether. It’s possible, however, to get the same result by using a standard for
loop rather than the newer forEach
.
Encapsulating Validation Logic in a Service Component
Of course, it’s ridiculously simple (and very, very tempting, to be frank) to start dropping Validator
instances here and there, and validate the constrained classes in multiple places, even in the wrong ones! But that would be a flagrant violation of the DRY Principle (aka a WET solution) that would lead to duplicated code across several layers. Yes, definitely bad design.
Instead, it’d be a lot more effective to encapsulate Hibernate Validator inside the boundaries of a decoupled service component, which could be potentially reused everywhere. In a nutshell, all that we need to do for wrapping it behind the fences of such a service component is a simple contract, defined through an interface, and a broadly generic implementation:
public interface EntityValidator<T> {
Set<ConstraintViolation<T>> validate(T t);
}
public class BaseEntityValidator<T> implements EntityValidator<T> {
protected final Validator validator;
public BaseEntityValidator(Validator validator) {
this.validator = validator;
}
public Set<ConstraintViolation<T>> validate(T t) {
return validator.validate(t);
}
}
By using plain delegation, we’ve created a working validation module, which takes a JSR 303-compliant implementation in the constructor, and uses its validate()
method for validating a given object of type T
. The only detail worth pointing here is that the method in question returns a set containing the corresponding constraint violation objects, after the object has been properly validated.
At first glance, the BaseEntityValidator
class looks quite simple. But this is just a misleading impression, trust me. The class not only takes advantage of interface-based polymorphism (aka subtype polymorphism), it also acts as an adapter to the validator itself, hence making it possible to selectively expose the validator’s native methods to client code and even add domain-specific ones.
If you’re wondering how to use the BaseEntityValidator
class to validate a user object, here’s how the process should be performed:
// ideally the validator is injected but in this case we’ll create it explicitly
EntityValidator<User> userValidator = new BaseEntityValidator<>(
Validation.buildDefaultValidatorFactory().getValidator());
User user = new User("John Doe", "no-email", "");
validator
.validate(user).stream()
.forEach(violation -> System.out.println(violation.getMessage()));
Additionally, there’s plenty of room for using the class in different contexts and scenarios. For instance, we could develop a basic web application, similar to the one I built in the Servlet API tutorial, and validate user data submitted through an HTML form in an effective and performant manner. As building such web application is definitively out of this post’s scope, the task will be left as homework for you, in case that you want to start pulling the reins of Hibernate Validator in the Web terrain.
Creating Custom Validation Constraints and Validators
At this point, it should be clear that Hibernate Validator is a hard-to-beat contender when it comes to validating domain objects in a straightforward fashion. But there’s more yet: Even when JSR 303 ships by default with a robust set of constraint violations, which will cover the most typical validation requirements of daily developers like you and me, it’s worth mentioning that its core functionality can be easily extended by means of custom validation constraints and validators.
In fact, creating a custom validation constraint and a matching custom validator is a no-brainer process that boils down to following these steps:
- Defining an annotation interface, which must specify the target(s) of the validation constraint, for how long the annotation information will be available to the compiler (aka the Retention Policy), and finally the custom validation class associated with the constraint.
- Creating the custom validation class itself.
As usual, a hands-on example is the best way to grasp the underlying logic of this process. So, let’s say we want to apply a custom email validation constraint to the User
class, instead of using the default one included with JSR 303. In that case, first we need to define the corresponding annotation interface, as follows:
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EmailValidator.class)
public @interface ValidEmail {
String message() default "Email must be a well-formed address";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default{};
}
As shown above, the ValidEmail
annotation tells the compiler that the constraint violation should be retained until runtime (@Retention(RetentionPolicy.RUNTIME)
) and be applicable to both methods and fields (@Target({ElementType.METHOD, ElementType.FIELD})
. It’s clear to see here that the @Constraint
annotation binds the custom constraint to a custom EmailValidator
class, which we will look at after examining the annotation’s methods.
Lastly, the message()
method defines, of course, the message that must be displayed if the constraint is violated.
Additionally, the groups()
method allows to specify which constraints should be validated when the validator is called, in a per-group basis. By default, all the constraints are checked within the default group, meaning that each constraint will be validated regardless of the target object’s lifecycle phase. It’s possible, however, to set up different groups in the form of simple interfaces, and specify which constraints must be validated according to a particular lifecycle phase. For brevity’s sake, the above example uses the default group.
Finally, the payload()
method can be used for attaching payload objects, which hold additional information about the constraints, and can be fetched when validating the target object.
Now let’s turn to the EmailValidator
class, which implements the actual verification. Here’s how a naive implementation might look:
public class EmailValidator implements ConstraintValidator<ValidEmail, String> {
private static final Pattern VALID_EMAIL_PATTERN = Pattern.compile(
"^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$",
Pattern.CASE_INSENSITIVE);
@Override
public void initialize(ValidEmail constraintAnnotation) {
// can be used to set the instance up for validation
}
@Override
public boolean isValid(
String email, ConstraintValidatorContext constraintValidatorContext) {
Matcher matcher = VALID_EMAIL_PATTERN.matcher(email);
return matcher.find();
}
}
There are only a couple of details worth highlighting here: The first one is that the custom validators must implement the native ConstraintValidator
interface and specify as type parameters the type of the custom constraint (here ValidEmail
) and the type being constrained (String
). The second detail is the isValid()
method itself, which validates the supplied email address.
Last but not least, the declaration of the email
field within the User
class must be refactored, that way it can bind the custom validation constraint to the customEmailValidator
class, as follows:
@ValidEmail
private String email;
Finally, validating a user object looks exactly the same as when using the default email validator.
That was pretty easy to understand, right? Of course, I’m not saying that you’ll always need to mess up your life by creating custom constraints and validators, as this would downgrade the functionality of Java Beans Validation to nearly zero. Nevertheless, from a design point of view it is useful to know that the standard offers a decent level of customization.
Conclusions
At this point, you’ve learned the basics of how to use Java Beans Validation and Hibernate Validator side by side, in order to validate your domain objects in a straightforward way. In addition, you saw that’s really easy to extend the specification’s core functionality with custom constraints and custom validators, in those use cases where the default ones just won’t fit your personal needs. Moreover, keep in mind that JSR 303 is work in progress moving at a pretty fast pace (in fact the release of Java Beans Validation 2.0 is just around the corner), so make sure to stay up to date with the latest news.
So, is that all we need to know when it comes to exploiting the functionality that the standard brings to the table? Well, from a starting point it is, indeed. As with many other language-related features, though, it’s not a panacea for healing all the potential validation ills your code can suffer from, so using it in a proper and conscious way is, as usual, up to us.
Quite possibly, the biggest benefit of using the specification lies on the fact that it allows to easily implement highly-decoupled validation components, which can be reused literally everywhere. If that sole argument isn’t compelling enough to get you started using Java Beans Validation right away, certainly nothing will be. So, what are you waiting for?
Frequently Asked Questions (FAQs) on Effective Domain Model Validation with Hibernate Validator
What is the Hibernate Validator and why is it important?
Hibernate Validator is a powerful tool that provides an implementation of the Bean Validation specification (JSR 303). It allows developers to ensure that the data within their applications adheres to certain rules or constraints, thereby maintaining data integrity and reducing the likelihood of errors. It is important because it simplifies the process of data validation, making it easier to maintain and debug applications.
How do I set up Hibernate Validator in my project?
To set up Hibernate Validator in your project, you need to add the Hibernate Validator dependency to your project’s build file. If you’re using Maven, you can add the following dependency to your pom.xml file:<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.13.Final</version>
</dependency>
After adding the dependency, you can use the Hibernate Validator API in your project.
How do I create custom constraints with Hibernate Validator?
Creating custom constraints with Hibernate Validator involves defining a new annotation and a corresponding ConstraintValidator. The annotation defines the constraint, and the ConstraintValidator implements the validation logic. Once you’ve defined your custom constraint, you can use it in your domain model just like any other constraint annotation.
Can I validate method parameters and return values with Hibernate Validator?
Yes, Hibernate Validator supports method-level validation. This allows you to validate the parameters of a method and the method’s return value. To enable method-level validation, you need to annotate the method or its parameters with constraint annotations and then validate the method using a Validator.
How do I handle validation errors in Hibernate Validator?
When a validation fails, Hibernate Validator throws a ConstraintViolationException. This exception contains a set of ConstraintViolation objects, each representing a failed validation. You can catch this exception and handle the validation errors in a way that suits your application.
Can I use Hibernate Validator with Spring?
Yes, Hibernate Validator integrates well with the Spring framework. Spring’s DataBinder uses Hibernate Validator by default to validate objects. You can also use Spring’s @Validated annotation to trigger validation in your Spring MVC controllers.
How do I internationalize validation messages in Hibernate Validator?
Hibernate Validator supports internationalization of validation messages through the use of message bundles. You can define your validation messages in a properties file and then reference these messages in your constraint annotations using the message attribute.
Can I validate collections with Hibernate Validator?
Yes, Hibernate Validator allows you to validate collections. You can annotate a collection with a constraint annotation, and Hibernate Validator will apply the constraint to each element in the collection.
How do I disable validation for a specific field in Hibernate Validator?
To disable validation for a specific field, you can use the @Valid annotation on the field and then set the validation groups to an empty group. This will effectively disable validation for that field.
Can I use Hibernate Validator to validate JSON objects?
While Hibernate Validator does not directly support JSON validation, you can convert your JSON objects to Java objects and then validate these Java objects using Hibernate Validator. There are several libraries available, such as Jackson, that can help with this conversion.
Alejandro Gervasio is a senior System Analyst from Argentina who has been involved in software development since the mid-80's. He has more than 12 years of experience in PHP development, 10 years in Java Programming, Object-Oriented Design, and most of the client-side technologies available out there.