Using Java Bean Validation for Method Parameters and Return Values

Share this article

Using Java Bean Validation for Method Parameters and Return Values

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?

"Use Java Bean Validation to validate method calls"

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.

  1. The @InterceptorBinding annotation: required for binding metadata, such as the RetentionPolicy and the applicable targets, to the custom interceptor annotation @MethodInterceptorBinding.
  2. The interceptor class: a regular class, with the @Interceptor and @MethodInterceptorBinding annotations, marking it as an interceptor.
  3. 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, the validateMethodInvocation() method interposes between invocations to the constrained methods of the User class and the caller.
  4. Interceptor implementation: A Validator object is injected into the interceptor class with CDI, which is used to get an instance of ExcecutableValidator and validate constraints in method arguments and return values.
  5. 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:

  1. Control flow only reaches the method’s body if client code has fulfilled the method’s preconditions.

  2. 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.

Frequently Asked Questions (FAQs) on Java Bean Validation for Method Parameters and Return Values

What is the purpose of Java Bean Validation?

Java Bean Validation is a framework that allows developers to ensure that the objects in their applications meet certain criteria or constraints. This is done through the use of annotations that can be applied to fields, methods, and classes. The validation process can be automated, reducing the amount of manual error checking code that needs to be written and making the code cleaner and easier to maintain.

How can I apply constraints to method parameters in Java Bean Validation?

Constraints can be applied to method parameters in Java Bean Validation by using the @Valid annotation. This annotation can be placed before the parameter in the method signature. When the method is called, the validation framework will check that the argument passed to the method meets the constraints specified by the @Valid annotation.

Can I apply multiple constraints to a single method parameter?

Yes, you can apply multiple constraints to a single method parameter. This is done by placing multiple constraint annotations before the parameter in the method signature. The validation framework will check that the argument passed to the method meets all of the specified constraints.

How can I validate the return value of a method?

The return value of a method can be validated by placing the @Valid annotation after the method signature. When the method is called, the validation framework will check that the value returned by the method meets the constraints specified by the @Valid annotation.

What happens if a constraint is violated?

If a constraint is violated, the validation framework will throw a ConstraintViolationException. This exception can be caught and handled in your code, allowing you to take appropriate action when a constraint is violated.

Can I create custom constraints?

Yes, you can create custom constraints in Java Bean Validation. This is done by creating a new annotation and a corresponding ConstraintValidator implementation. The annotation defines the constraint, and the ConstraintValidator implementation checks that an object meets the constraint.

How can I disable validation for a particular method or class?

Validation can be disabled for a particular method or class by using the @ValidateOnExecution annotation. This annotation can be placed before the method or class declaration, and it takes a Mode argument that specifies when validation should be performed.

Can I use Java Bean Validation with other frameworks?

Yes, Java Bean Validation can be used with other frameworks. Many popular frameworks, such as Spring and Hibernate, have built-in support for Java Bean Validation.

What are the benefits of using Java Bean Validation?

Java Bean Validation provides several benefits. It reduces the amount of manual error checking code that needs to be written, making your code cleaner and easier to maintain. It also provides a consistent approach to validation across your application, which can improve code readability and reduce the likelihood of errors.

Are there any limitations or drawbacks to using Java Bean Validation?

While Java Bean Validation is a powerful tool, it does have some limitations. For example, it can only validate the state of an object at a particular point in time, and it cannot validate the sequence of method calls or the interaction between multiple objects. Additionally, while the validation annotations are easy to use, creating custom constraints can be more complex.

Alejandro GervasioAlejandro Gervasio
View Author

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.

Java EEvalidation
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week