Bean validation (JSR 303 and JSR 349) provides facilities to perform data validation in Java EE and SE applications. Liberty supports bean validation for Java EE applications. This article explains what bean validation is, how you can use it, and how it is integrated and supported in Liberty and WebSphere Developer Tools (WDT)

What is bean validation?

Validating data in Java EE applications (or, in general, any good application) is a common need; for example, validating data when it is entered by the user, when it is passed from one layer of the application to another, before it is persisted, etc. Historically, code to validate data has been intermingled with business logic code or the data model layer, and is often duplicated among the different layers that comprise an application.

The bean validation specification (JSR 303 for version 1.0 and JSR 349 for version 1.1) provides developers with APIs and metadata to define, in a standard way, validation rules (in the form of constraints) for data stored in JavaBeans (or beans).

A developer can use the constraints in the specification (version 1.1 includes 13 constraints), or can define new constraints. They can then apply those constraints to fields or methods of a bean (using annotations or XML deployment descriptors, as we will see later), reducing duplication of validation code.

The bean validation implementation validates that the bean’s data complies with the constraints defined, or reports ConstraintViolations, which include information like the error message, the bean validated, the path to the property that has invalid data, etc. You no longer need to check if a field is null, or if its value is in a valid range; bean validation does it for you!

WebSphere Liberty supports bean validation; you can add the features beanValidation-1.0 or beanValidation-1.1 to your server.xml, and that’s all you need to do.

WDT provides tools for bean validation too, including validation for built-in constraints and editors for the bean validation XML deployment descriptors.

An example of calling the bean validation API

The following example shows how to call the bean validation API to validate a bean. Suppose we have a bean SoccerPlayer, and we want to validate that:

  • The first name and last name are not null, and are at least 5 character long.
  • The age is a positive number, between 16 and 50.

Your bean could look like this:

package com.ibm.ws.beanvalidation.example.model;

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

public class SoccerPlayer {

    @NotNull
    @Size(min = 5)
    private String firstName;

    @NotNull
    @Size(min = 5)
    private String lastName;

    @Max(50)
    @Min(16)
    private int age;
    
    private String position;

    public SoccerPlayer() {
    }
    
    // Getters and setters omitted 
}

In this example, we annotated the fields of the bean. You can annotate the properties (getter methods) instead, but the specification recommends to stick with one approach; either use fields or properties, but not both.

The following servlet creates a SoccerPlayer and validates it using the bean validation API. If data on the bean violates any of the constraints defined on the bean, the violations are reported to the user:

package com.ibm.ws.beanvalidation.example.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Set;

import javax.inject.Inject;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import com.ibm.ws.beanvalidation.example.model.SoccerPlayer;

/**
 * Servlet implementation class PersonCreator
 */
@WebServlet("/PlayerCreator")
public class PlayerCreator extends HttpServlet {
    private static final long serialVersionUID = 1L;

    @Inject
    Validator validator;
    
    /**
     * @see HttpServlet#HttpServlet()
     */
    public PlayerCreator() {
        super();
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
     *      response)
     */
    protected void doGet(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        createPlayer(request, response);
    }

    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
     *      response)
     */
    protected void doPost(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        createPlayer(request, response);
    }

    private void createPlayer(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {

        String firstName = request.getParameter("firstName");
        String lastName = request.getParameter("lastName");
        int age = Integer.parseInt(request.getParameter("age"));

        // Build the player...
        SoccerPlayer player = new SoccerPlayer();
        player.setFirstName(firstName);
        player.setLastName(lastName);
        player.setAge(age);

        // Validate the player before we use it...

        // CDI is used to inject the validator, but this is how you would get it using JNDI.
        // You need to add the jndi-1.0 feature to server.xml
                
        /*InitialContext context = null;
        Validator validator = null;
        try {
            context = new InitialContext();
            validator = (Validator) context.lookup("java:comp/Validator");
        } catch (NamingException e) {
            e.printStackTrace();
        } */

        Set<ConstraintViolation<SoccerPlayer>> violations = validator
                .validate(player);

        if (!violations.isEmpty()) {
            // Something is wrong with the data in the bean, report to user (in
            // this example, just print it)
            response.setContentType("text/html");
            PrintWriter out = response.getWriter();
            for (ConstraintViolation<SoccerPlayer> violation : violations) {
                out.println(violation);
                out.println("<br>");
            }
            return;
        }
        // The bean is valid, process it....
    }
}

The code above uses the @Inject annotation to get a reference to the Validator. So make sure the CDI feature (cdi-1.2) is added to your server.xml if you want to test this code.

When this servlet is called using the following URL http://localhost:9080/BeanValidationSamples/PlayerCreator?firstName=Rob&lastName=Sanchez&age=15, we see the constraints violations found by bean validation:

ConstraintViolationImpl{rootBean=com.ibm.ws.beanvalidation.example.model.SoccerPlayer@f82fdc1e, propertyPath='firstName', message='size must be between 5 and 2147483647', leafBean=com.ibm.ws.beanvalidation.example.model.SoccerPlayer@f82fdc1e, value=Rob}
ConstraintViolationImpl{rootBean=com.ibm.ws.beanvalidation.example.model.SoccerPlayer@f82fdc1e, propertyPath='age', message='must be greater than or equal to 16', leafBean=com.ibm.ws.beanvalidation.example.model.SoccerPlayer@f82fdc1e, value=15} 

Nice! We indicated the validation rules and bean validation did the rest! You might say “Well, you had to use javax.validation.Validator to explicitly validate the bean. Could the validation be performed automatically?” Well, yes, it can be done automatically, if you combine bean validation and other Java EE specifications. We’ll see later, so keep reading.

Note that you can validate more than fields in a bean; you can put constraints on a method’s parameters, on a method’s return value, and even on classes! (support for validation on method’s parameters and method’s return value was added in version 1.1).

Creating constraints

As I mentioned before, bean validation includes a set of validation constraints (@NotNull, @Null, @AssertTrue, @AssertFalse, @Min, @Max, @DecimalMin, @DecimalMax, @Size, @Digits, @Past, @Future and @Pattern), but what happens if they do not fit your needs? You can create your own constraints.

We now want to validate that the value of the position field of the SoccerPlayer bean is one of a set of predefined values. We start creating an annotation:

 
package com.ibm.ws.beanvalidation.example.constraints;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

@Documented
@Constraint(validatedBy = PositionValidator.class)
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidPosition {
    
    String[] value() default {"goalkeeper", "defender", "midfielder", "forward"} ;
    String message() default "The position is not valid";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

This annotation is applicable to fields and methods (in case we want to use it in a getter method), and provides a default set of valid positions.
Note that the @Constraint annotation includes a validatedBy attribute, which indicates that class that will perform the validation. We need to create that class now:

package com.ibm.ws.beanvalidation.example.constraints;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class PositionValidator implements ConstraintValidator<ValidPosition, String> {

    Set<String> validPositions = null;
    
    @Override
    public void initialize(ValidPosition annotation) {
        validPositions = new HashSet<String>(Arrays.asList(annotation.value()));  
    }

    @Override
    public boolean isValid(String position, ConstraintValidatorContext context) {      
        if (!validPositions.contains(position)){            
            return false;
        }
        return true;
    }
}

The class implements the ConstraintValidator interface, and we specify the type parameters, which in this case are the annotation we created previously, and the type for which this validator is applicable (String in this example).

Now, we update the SoccerPlayer bean to validate the position field:

    ...
    @Max(50)
    @Min(16)
    private int age;
    
    @ValidPosition
    private String position;

    public SoccerPlayer() {
    }
    ...

And update the servlet to specify an invalid position:

    ...
    // Build the player...
    SoccerPlayer player = new SoccerPlayer();
    player.setFirstName(firstName);
    player.setLastName(lastName);
    player.setAge(age);
    player.setPosition("bench");

    // Validate the player before we use it..
    ...

and if we call the servlet again, we now see a third constraint violation, corresponding to our custom validator:

ConstraintViolationImpl{rootBean=com.ibm.ws.beanvalidation.example.model.SoccerPlayer@f82fdc1e, propertyPath='firstName', message='size must be between 5 and 2147483647', leafBean=com.ibm.ws.beanvalidation.example.model.SoccerPlayer@f82fdc1e, value=Rob}
ConstraintViolationImpl{rootBean=com.ibm.ws.beanvalidation.example.model.SoccerPlayer@f82fdc1e, propertyPath='position', message='The position is not valid', leafBean=com.ibm.ws.beanvalidation.example.model.SoccerPlayer@f82fdc1e, value=bench}
ConstraintViolationImpl{rootBean=com.ibm.ws.beanvalidation.example.model.SoccerPlayer@f82fdc1e, propertyPath='age', message='must be greater than or equal to 16', leafBean=com.ibm.ws.beanvalidation.example.model.SoccerPlayer@f82fdc1e, value=15}

Using XML deployment descriptors

So far, we have used annotations to specify constraints on beans. But, for XML fans, we can use XML files to either override the existing annotation constraints, or add additional constraints.

In this example, we will change the minimum length of the name of our soccer player to be 2 instead of 5, using the XML deployment descriptor. To do this, we need to create 2 files: META-INF/validation.xml, and a constraint mapping file. WDT provides menu options to create these files.

To create validation.xml, you can right-click a project targeted to Liberty, and select Java EE Tools > Create Bean Validation configuration file. This will create the file, and will open it in the Bean Validation Configuration editor.


WhatIsBeanValidationFig3

From that editor, we can create the constraint mapping file. Select the Validation Configuration node, and click Add > Constraint Mapping. Then click the Reference link, optionally change the name of the file, and click Finish.


WhatIsBeanValidationFig4

The file will open in the Bean Validation constraints mapping editor. Using the Design or the Source tab, change the XML to look like this:

<?xml version="1.0" encoding="UTF-8"?>
<constraint-mappings version="1.1"
	xmlns="http://jboss.org/xml/ns/javax/validation/mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://jboss.org/xml/ns/javax/validation/mapping http://jboss.org/xml/ns/javax/validation/mapping/validation-mapping-1.1.xsd ">
	<bean class="com.ibm.ws.beanvalidation.example.model.SoccerPlayer" ignore-annotations="false">
		<field name="firstName" ignore-annotations="true">
			<constraint annotation="javax.validation.constraints.NotNull" />
			<constraint annotation="javax.validation.constraints.Size">
				<element name="min">2</element>
			</constraint>
		</field>
	</bean>
</constraint-mappings>

And now, when we call the servlet using the same URL (http://localhost:9080/BeanValidationSamples/PlayerCreator?firstName=Rob&lastName=Sanchez&age=15), we see only two constraint violations:

ConstraintViolationImpl{rootBean=com.ibm.ws.beanvalidation.example.model.SoccerPlayer@6a6be452, propertyPath='position', message='The position is not valid', leafBean=com.ibm.ws.beanvalidation.example.model.SoccerPlayer@6a6be452, value=bench}
ConstraintViolationImpl{rootBean=com.ibm.ws.beanvalidation.example.model.SoccerPlayer@6a6be452, propertyPath='age', message='must be greater than or equal to 16', leafBean=com.ibm.ws.beanvalidation.example.model.SoccerPlayer@6a6be452, value=15} 

Integration with other Java EE specifications

In the previous examples, we have called javax.validation.Validator to validate beans. Bean validation integrates with other specifications (like CDI, JPA and JSF), so that beans are validated automatically at certain points. In this section, we modify our example to use the integration of bean validation with CDI.

Bean validation and CDI

As mentioned in the Bean Validation 1.1 specification, section 10.3.3,

Bean Validation requires that CDI beans support constructor and method validation as defined in Section 10.1.2.
Validation must happen at the equivalent time an interceptor occurs when having priority Interceptor.
Priority.PLATFORM_AFTER+800, in other words priority of 4800.

In our example, we create a CDI bean that will process a SoccerPlayer bean, and validate it automatically:

package com.ibm.ws.beanvalidation.example.cdi.beans;

import javax.enterprise.context.RequestScoped;
import javax.validation.Valid;
import com.ibm.ws.beanvalidation.example.model.SoccerPlayer;

@RequestScoped
public class SoccerPlayerProcessor {    
    
    public void processPlayer(@Valid SoccerPlayer player){
        // Do stuff with the player.
    }   
}

In the previous code, the parameter in method processPlayer() has the @Valid annotation, meaning that when this method is called, the SoccerPlayer will be validated, and a ConstraintViolationException will be thrown if something is not correct in the bean.

Now, we change the code in our servlet to inject the SoccerPlayerProcessor CDI bean, call its method, and catch the ConstraintViolationException:

...

/**
 * Servlet implementation class PersonCreator
 */
@WebServlet("/PlayerCreator")
public class PlayerCreator extends HttpServlet {
    private static final long serialVersionUID = 1L;

    @Inject
    Validator validator;
    @Inject
    SoccerPlayerProcessor playerProcessor;
    
...
    private void createPlayer(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {

        String firstName = request.getParameter("firstName");
        String lastName = request.getParameter("lastName");
        int age = Integer.parseInt(request.getParameter("age"));

        // Build the player...
        SoccerPlayer player = new SoccerPlayer();
        player.setFirstName(firstName);
        player.setLastName(lastName);
        player.setAge(age);
        player.setPosition("bench");             

        // Process the bean...
        try {
            playerProcessor.processPlayer(player);
        }
        catch (ConstraintViolationException e){
            Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
            response.setContentType("text/html");
            PrintWriter out = response.getWriter();
            for (ConstraintViolation<?> violation : violations) {
                out.println(violation);
                out.println("<br>");
            }
            return;
        }
    }
}

And now, we call our servlet again, and we see the same constraints:

ConstraintViolationImpl{rootBean=com.ibm.ws.beanvalidation.example.cdi.beans.SoccerPlayerProcessor$Proxy$_$$_WeldSubclass@5920624e, propertyPath='processPlayer.arg0.age', message='must be greater than or equal to 16', leafBean=com.ibm.ws.beanvalidation.example.model.SoccerPlayer@27ca73b3, value=15}
ConstraintViolationImpl{rootBean=com.ibm.ws.beanvalidation.example.cdi.beans.SoccerPlayerProcessor$Proxy$_$$_WeldSubclass@5920624e, propertyPath='processPlayer.arg0.position', message='The position is not valid', leafBean=com.ibm.ws.beanvalidation.example.model.SoccerPlayer@27ca73b3, value=bench} 

This was an introduction to bean validation, explaining what bean validation is, and showing some examples. But there is a lot more; bean validation has a lot of features, beyond the scope of this introductory post. We encourage you explore the JSR 349 specification, which is very well written and has lot of examples.

Join The Discussion

Your email address will not be published. Required fields are marked *