Validate Object Graphs with Java Bean Validation’s @Valid Annotation
Table of Contents
The Java Bean Validation’s @Valid
constraint annotation makes sure that when an object is validated, the validation recurses to all fields that are annotated with @Valid
. This makes it really easy to perform the usually complex task of validating entire object graphs.
To make sense of this post you should know the basics of how to use Java Bean Validation.
Object Graph Validation in One Step With @Valid
To better understand why the @Valid
annotation is such a valuable feature, I will start by showing you what you have to do to validate an object graph that doesn’t use it.
Bloated Code without @Valid
Consider the case of a constrained class A
that declares a dependency on another set of constrained classes B
and C
. What we have here is just a simple object graph, where each collaborator on this association has its own set of validation constraints. Without the @Valid
annotation, validating the graph is a pretty cumbersome task, as each instance of A
, B
, and C
has to be validated separately, through multiple calls to the validate()
method of the Validator
interface implementer.
Consider the following classes:
public class AccountProcessor {
@NotEmpty(message = "Account number may not be empty")
@Size(min = 8, max = 8,
message = "Account number must be an 8-digit string")
private String accountNumber;
public AccountProcessor(String accountNumber) {
this.accountNumber = accountNumber;
}
public void processAccount() {
// process the account here
}
}
public class PaymentService {
private AccountProcessor accountProcessor;
@NotEmpty(message = "Payment method may not be empty")
private String paymentMethod;
public PaymentService(AccountProcessor accountProcessor, String paymentMethod) {
this.accountProcessor = accountProcessor;
this.paymentMethod = paymentMethod;
}
}
At this point, we’ve managed to create a basic object graph with PaymentService
using AccountProcessor
. Without the @Valid
annotation, the graph has to be validated on a per-object basis, as follows:
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
AccountProcessor accountProcessor = new AccountProcessor("");
PaymentService paymentService = new PaymentService(accountProcessor, "");
validator
.validate(accountProcessor)
.stream()
.forEach(violation -> System.out.println(violation.getMessage()));
validator
.validate(paymentService)
.stream()
.forEach(violation -> System.out.println(violation.getMessage()));
In this case, neither the AccountProcessor
instance nor its PaymentService
have been correctly initialized and taken together both will raise three constraint violations.
While the validation works as expected, the code is bloated with having to call validate
separately for each instance.
Using @Valid
to Validate an Object Graph
To improve the validation code, let’s refactor the PaymentService
class and bind the @Valid
annotation to its AccountProcessor
field:
public class PaymentService {
@Valid
private AccountProcessor accountProcessor;
// other class members and methods as before
}
The impact of using the @Valid
annotation can be spotted in a snap here:
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
AccountProcessor defaultAccountProcessor = new DefaultAccountProcessor("");
PaymentService paymentService = new PaymentService(defaultAccountProcessor, "");
validator
.validate(paymentService)
.stream()
.forEach(violation -> System.out.println(violation.getMessage()));
The code is more compact and streamlined but will result in the same validation result as the longer block before. Whenever an instance of PaymentService
gets validated, AccountProcessor
will be validated recursively. We kill two birds with one stone!
From a client code’s perspective, the validation is apparently performed through a single call to validate()
, which is the right way to do it, as this neatly hides from the developer the complexities involved in scanning the whole object graph structure. Internally, though, the method is actually called recursively while the validator traverses the tree from the root object to the leaf objects. Moreover, the use of @Valid
isn’t restricted to just class fields, as it can be utilized in collections and arrays as well.
This is object graph validation made easy.
Summary
The @Valid
annotation is a key feature of Bean Validation, as it allows to validate object graphs with a single call to the validator. To make use of it all fields that should be recursively checked should be annotated with @Valid
.
Another interesting feature that goes beyond basic bean validation is validating individual fields and values with validateProperty()
and validateValue()
. The API has of course much more to offer, so make sure to check the official docs and keep up to date with the latest news.