Using Java Bean Validation for Method Parameters and Return Values
Table of Contents
Java’s static type system is a robust mechanism that lets you specify a method’s preconditions and postconditions (from the caller’s perspective: what to supply and what to expect) with the compiler assuring that these conditions are fulfilled at run time. Java Bean Validation takes this constrain model further and closer to a specific domain. It provides a fully-fledged validation API, which not only allows to validate the fields of a class through a set of intuitive validation constraints. As we will see in this post, it also gives developers the ability to reuse these constraints in arguments and return values in both regular methods and constructors and validate them in a design-by-contract fashion – either manually or automatically via Java EE interceptors.
In case you want to take a peek at the standard’s core features, have a look at this introductory article. The validations explained there use the Validator
API while this article here calls methods provided by the newer ExecutableValidator
interface – both are part of standard-compliant implementations like Hibernate Validator and Apache BVal.
Validating Method Arguments
The first method that we’ll cover in this roundup is validateParameters()
, which validates the arguments of a given method. Consider the example below, which binds some basic constraints to the setters of the following User
class:
public class User {
private String name;
private String email;
public User(){}
public void setName(
@NotEmpty(message = "Name may not be empty") String name) {
this.name = name;
}
public void setEmail(
@Email(message = "Email must be a well-formed email address") String email) {
this.email = email;
}
// getters for name and email
}
It’s really easy to track down the contract agreed by the target class with the caller without having to scan the class from top to bottom and take a deeper look at method implementations. Simply put, the precondition of setName()
is a non-empty string, while the precondition of setEmail()
is a well-formed email address.
The contract, of course, must be validated at some point. To accomplish this, the standard provides the aforementioned validateParameters()
method:
ExecutableValidator executableValidator = Validation
.buildDefaultValidatorFactory()
.getValidator()
.forExecutables();
User user = new User();
try {
Method setName = user.getClass()
.getMethod("setName", String.class);
executableValidator
.validateParameters(user, setName, new Object[]{""}).stream()
.map(ConstraintViolation::getMessage)
.forEach(System.out::println);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
The first thing to stress here is that all the methods that perform the validation of method parameters and return values (this applies to constructors too) are part of the ExecutableValidator
API, instead of the classic Validator
. The second one is the mechanics of the validation process per se: Once an implementer of ExecutableValidator
has been created, the whole validation process just boils down to calling the proper validation method with the required arguments.
In the above example, setName()
‘s preconditions are validated through the validateParameters()
method, which takes an instance of the target class, the method to be validated, and the arguments stored in an array of objects. Considering that in this case the array contains only an empty string, the precondition of setName()
isn’t fulfilled, which raises a constraint violation.
How about validating the precondition of setEmail()
instead?
try {
Method setEmail = user.getClass()
.getMethod("setEmail", String.class);
executableValidator
.validateParameters(user, setEmail, new Object[]{""}).stream()
.map(ConstraintViolation::getMessage)
.forEach(System.out::println);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
That’s pretty straightforward to understand.
Validating Method Return Values
Along the same line, it’s possible to specify a method’s postcondition and validate it with the validateReturnValue()
method. To demonstrate how easy is to validate a method’s return value, let’s refactor the earlier User
class, like this:
public class User {
private String name;
private String email;
public User(){}
// setter and getter for name, setter for email
@Email(message = "Email must be a well-formed email address")
public String getEmail() {
return email;
}
}
In this case, the postcondition of the getEmail()
method is a well-formed email address. Here’s how to validate the method’s postcondition with the validateReturnValue()
method:
User user = new User();
try {
Method getEmail = user.getClass().getMethod("getEmail");
executableValidator
.validateReturnValue(user, getEmail, "no-email").stream()
.map(ConstraintViolation::getMessage)
.forEach(System.out::println);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
As expected, this will pop up a constraint violation too, as the postcondition of the getEmail()
method isn’t satisfied by the String "no-email"
, which is passed to validateReturnValue()
.
Validating Constructor Arguments
Similarly to methods, the standard allows to validate arguments in constructors through the validateConstructorParameters()
method. It does exactly what it promises at face value: yes, it validates constructor parameters! That’s yet another clear sign of a well-designed API.
In order to use the method in question, the User
class should be refactored as follows:
public class User {
private String name;
private String email;
public User(){}
public User(
@NotEmpty(message = "Name may not be empty") String name,
@Email(message = "Email must be a well-formed email address") String email) {
this.name = name;
this.email = email;
}
// setters and getters for name and email
}
As shown above, the constructor arguments have been constrained by using a couple of annotations, as we just did above with both method arguments and return values. So, here’s how to validate them:
User user = new User("", "");
try {
Constructor constructor = user.getClass()
.getConstructor(new Class[] {String.class, String.class});
executableValidator
.validateConstructorParameters(constructor, new Object[]{"", ""})
.stream()
.map(ConstraintViolation::getMessage)
.forEach(System.out::println);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
Using the validateConstructorParameters()
method is just as simple as passing to it the constructor of the target class along with the corresponding arguments. In this case, I deliberately passed in an array of empty strings, which causes the method to trigger two constraint violations.
At this point, it’s easy to see why Bean Validation is a feature-rich framework, with a huge variety of options to pick up from when it comes to validating objects. It provides full support for validation of method arguments, return values and constructor parameters, with all these handy features implemented around a neat design-by-contract model. What else could we ask for?
Automatic Validation with Java EE Interceptors
While what we discussed so far sounds all well and fine, let’s think about it: What’s the point of having such a powerful arsenal of validation methods under the belt, if we always end up calling them manually? Yikes!
The good news is that we don’t have to go through the burdens of manual validation, unless we deliberately want to punish ourselves with such an unnecessary effort! As I stated in the introduction, a typical approach to tackle this issue is enabling automatic method validation with Java EE interceptors.
While the name may sound intimidating, interceptors are just regular classes that are used to intercept method calls or lifecycle events of one or multiple target classes. We could put this functionality to work for us and use an interceptor for encapsulating validation logic in one place and intercepting calls to constrained methods in target objects, in order to validate the corresponding constraints. In a nutshell, we’ll be implementing the long-awaited automatic method validation!
Look at the following domain class:
public class User {
private String name;
private String email;
public User(){}
@Interceptors(MethodInterceptor.class)
public void setName(@NotEmpty(message = "Name may not be empty") String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setEmail(String email) {
this.email = email;
}
@Interceptors(MethodInterceptor.class)
@Email(message = "Email must be a well-formed address")
public String getEmail() {
return email;
}
}
As shown above, the User
class now has a constrained setter and a constrained getter. The only detail actually worth noting here is that the constrained methods are bound to a MethodInterceptor
interceptor by means of the @Interceptors
annotation, followed by the interceptor class, enclosed in parenthesis. The annotation links the constrained methods to the interceptor, which means that whenever the methods are invoked, the calls can be intercepted and the constraints can be validated by following a specific strategy.
Here’s how a typical method interceptor might be implemented:
@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface MethodInterceptorBinding { }
@Interceptor
@MethodInterceptorBinding
public class MethodInterceptor {
@Inject
private Validator validator;
@AroundInvoke
public Object validateMethodInvocation(InvocationContext ctx)
throws Exception {
Set<ConstraintViolation<Object>> violations;
ExecutableValidator executableValidator = validator.forExecutables();
violations = executableValidator.validateParameters(
ctx.getTarget(), ctx.getMethod(), ctx.getParameters());
processViolations(violations);
Object result = ctx.proceed();
violations = executableValidator.validateReturnValue(
ctx.getTarget(), ctx.getMethod(), result);
processViolations(violations);
return result;
}
private void processViolations(Set<ConstraintViolation<Object>> violations) {
violations.stream()
.map(ConstraintViolation::getMessage)
.forEach(System.out::println);
}
}
The example looks rather hard to grasp, but let’s dissect it in a few simpler steps to understand how it works.
- The
@InterceptorBinding
annotation: required for binding metadata, such as theRetentionPolicy
and the applicable targets, to the custom interceptor annotation@MethodInterceptorBinding
. - The interceptor class: a regular class, with the
@Interceptor
and@MethodInterceptorBinding
annotations, marking it as an interceptor. - The
@AroundInvoke
annotation: used to define an interceptor that intercepts invocations to the methods which is bound to (it’s possible to create a few more types of interceptors, but they’re omitted here for simplicity’s sake). In this case, thevalidateMethodInvocation()
method interposes between invocations to the constrained methods of theUser
class and the caller. - Interceptor implementation: A
Validator
object is injected into the interceptor class with CDI, which is used to get an instance ofExcecutableValidator
and validate constraints in method arguments and return values. - Interceptor output: When the interceptor validates the constrained methods, the potential constraint violations are printed out to the console through the
processViolations()
method.
Regarding processViolations()
printing to the console, the official documentation strongly encourages to throw a ConstraintViolationException
, wrapping the violations from within the interceptor when a method fails to pass the validation, which makes a lot of sense, as this assures the following:
-
Control flow only reaches the method’s body if client code has fulfilled the method’s preconditions.
-
Control flow returns to client code if the method’s post conditions are guaranteed.
I did not do that here to make the example a little easier to grasp.
With that said, a nice way to see how the use of interceptors automatically triggers method validation is as follows:
public class Application {
@Inject
private User user;
public void execute() {
user.setName("John");
user.setEmail("no-email");
user.getEmail();
}
}
As expected, calling setEmail()
and getEmail()
will result in two constraint violations being printed to the console, due to the transparent action of the interceptor.
If you feel in the proper mood and want to implement your own interceptors or even tweak the one shown above, first off make sure to give a read to the official docs.
Summary
In this post, we learned how to use the methods that are part of the ExecutableValidator
interface, in order to validate arguments and return values in both regular methods and constructors. In addition, we saw that it’s possible to integrate the standard with CDI and Java EE interceptors and trigger automatic method validation.
At first, the integration process looks fairly complex, but the effort of implementing an additional layer of interceptors is largely compensated with the benefits of encapsulating validation logic in one single place and implementing inversion of control. Last but not least, keep in mind that Bean Validation’s functionality isn’t just limited to validating method arguments and return values, as it offers a lot of other nifty features worth looking, such as validating individually class fields and object graphs with a single method call. So, if you’re interested in learning how to do all these things without having to climb a steep learning curve, you can check these shorter posts to learn how to validate individual properties and fields and how to use @Valid
to validate entire object graphs.