Validation in Java with a Custom Holder Class
Bean Validation
The JSR-303 “Bean Validation” specification (2007) defines a means of validating data within a Java application. Typically one would use a library such as Hibernate Validator to actually implement the specification. An example of usage on a model class within a project might be something like;
public class Book {
@NotNull
@Size(min = 2, max = 128)
private String title;
...
}
The JSR 303 standard defines a number of very commonly used annotations such as @NotNull
and others such as @Email
are provided by third-party libraries. Common Java infrastructure such as Hibernate (ORM) or SpringMVC (APIs) typically use this mechanism to validate data.
Holder Classes
From time to time, data in a software project has to be wrapped in a structure called a “holder” which adds additional information or context to some other data. A good example is the Optional
class from the core Java libraries which denotes if the value wrapped is present or not and provides some functional-style methods to act on the data. Here is an example of the Optional
class in action;
Optional.ofNullable(person)
.map(Person::getAge)
.filter(age -> age > 10)
.orElse(0);
Validating the Value in a Holder
Assuming a custom holder class Approved
, the question might arise how you can use data wrapped in the custom holder with JSR 303 validations. This, it transpires, is not too tricky as you can write an instance of javax.validation.valueextraction.ValueExtractor
that is able to extract the value to be validated from the holder; see the comments on the ValueExtractor
interface for further guidance.
A project’s build product is able to incorporate a classpath resource at /META-INF/validation.xml
which can contain <value-extractor>...</value-extractor>
sections defining any number of ValueExtractor
implementations.
The model class can now express the validations on the holder using annotations in the following manner;
public class Book {
private Approved<@NotNull @Size(min = 2, max = 128) String> approvedTitle;
...
}
Validating without a ValueExtractor - A Work-Around
When working with some code-generation tools it might be difficult or impossible to manipulate the output code to yield the arrangement with the annotations as shown above. In this situation the best that might be able to be achieved is;
public class Book {
@NotNull
@Size(min = 2, max = 128)
private Approved<String> approvedTitle;
...
}
There does exist a work-around in this case, but it is more arduous and involves re-implementing the validation logic. Following the example code above, for each validation annotation that is required (eg: @NotNull
), it is necessary to implement a concrete instance of ConstraintValidator<NotNull, Approved<?>>
such as NotNullApprovedConstraintValidator
. The actual validation logic must then be implemented in the NotNullApprovedConstraintValidator
class.
To register these custom ConstaintValidator
implementations with the validation engine implementation, the project can define a classpath resource constraint-mappings.xml
containing items such as;
<constraint-definition annotation="javax.validation.constraints.NotNull">
<validated-by include-existing-validators="true">
<value>nz.co.lindesay.NotNullApprovedConstraintValidator</value>
</validated-by>
</constraint-definition>
This resource, in turn, should be referenced from the /META-INF/validation.xml
resource;
<constraint-mapping>nz/co/lindesay/constraint-mappings.xml</constraint-mapping>
This work-around is not ideal, but does provide a path forward for this situation.