Bean Validation in Java EE 7 – Creating Custom Constraints and Validations

SHARE & COMMENT :

In my previous post I showed how one can make use of the default constraints provided by the Bean Validation API in validatng the model data. Bean Validation API allows the developers to define their own constraints by creating a new annotation and writing the validator which is used to validate the value. In this post I will show you how to create custom constraints and then use it to validate your data. I am writing series of articles on Java EE 7 features.

To create a custom constraints you have to identify the following:

  • Where the constraint can be used i.e on method, field, another annotation, constructor, method parameter.
  • The validator which is used by the constraint to validate the value.

Before I go into the details of creating custom constraint, I will explain the model classes involved in this sample and also the validation constraints I want to impose on the data in my model classes. There are two model classes involved: MyPerson which holds the data for a person and MyAddress which holds the address of a person.

public class MyAddress {
  private String streetAddress;
  private String locality;
  private String city;
  private String state;
  private String country;
  private String pinCode;

  public MyAddress(String streetAddress, String locality, String city, String state, String country, String pinCode) {
    this.streetAddress = streetAddress;
    this.locality = locality;
    this.city = city;
    this.state = state;
    this.country = country;
    this.pinCode = pinCode;
  }

  public String getStreetAddress() {
    return streetAddress;
  }

  public void setStreetAddress(String streetAddress) {
    this.streetAddress = streetAddress;
  }

  public String getLocality() {
    return locality;
  }

  public void setLocality(String locality) {
    this.locality = locality;
  }

  public String getCity() {
    return city;
  }

  public void setCity(String city) {
    this.city = city;
  }

  public String getState() {
    return state;
  }

  public void setState(String state) {
    this.state = state;
  }

  public String getCountry() {
    return country;
  }

  public void setCountry(String country) {
    this.country = country;
  }

  public String getPinCode() {
    return pinCode;
  }

  public void setPinCode(String pinCode) {
    this.pinCode = pinCode;
  }
}

Now the MyPerson class uses default constraints for few of its fields and we will build a custom constraint for validating the address field which we call it as @Address. Lets look at the code for MyPerson class:

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import validators.Address;

public class MyPerson {
  @NotNull @Size(min=4)
  private String firstName;

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

  @Address
  private MyAddress address;

  public MyPerson(String firstName, String lastName, MyAddress address) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.address = address;
  }

  public String getFirstName() {
    return firstName;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }

  public MyAddress getAddress() {
    return address;
  }

  public void setAddress(MyAddress address) {
    this.address = address;
  }

}

Now that we have seen our model classes, let me dive into creating the custom constraint. One would have to do the following:

  1. Create annotation for using it in the fields or methods of the model class.
  2. Create a validator which is used for validating the value.

Creating a Validator

Before I show you how to create the validator, let me state the constraints which must be satisfied by every instance of MyAddress class:

  1. The address field in the MyPerson class should not be null
  2. The instance of MyAdress should have all the fields defined i.e not even a single field should be null.
  3. The pin code fields in MyAdress should be of atleast 6 characters.
  4. The country field in MyAddress should be of atleast 4 characters.

The validator has to implement the interface javax.validation.ConstraintValidator<A extends java.lang.annotation.Annotation,T> and override the initialize and isValid methods.

From the Java doc for javax.validation.ConstraintValidator<A extends java.lang.annotation.Annotation,T>: Defines the logic to validate a given constraint A for a given object type T.

When we override the isValid method we get a reference to the value which is assigned to the field for which we are writing the constraint which in this example is an instance of MyAddress class. The isValid method implementation will check for the constraints mentioned above and return a boolean value to indicate if the value satifies our constraints or not. Lets look at the imeplementation of the validator:

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import model.MyAddress;

public class AddressValidator implements ConstraintValidator<Address, MyAddress> {

  @Override
  public void initialize(Address constraintAnnotation) {
  }

  /**
   * 1. The address should not be null.
   * 2. The address should have all the data values specified.
   * 3. Pin code in the address should be of atleast 6 characters.
   * 4. The country in the address should be of atleast 4 characters.
   */
  @Override
  public boolean isValid(MyAddress value, ConstraintValidatorContext context) {
    if (value == null) {
      return false;
    }

    if (value.getCity() == null || value.getCountry() == null || value.getLocality() == null
            || value.getPinCode() == null || value.getState() == null || value.getStreetAddress() == null) {
      return false;
    }

    if (value.getPinCode().length() < 6) {
      return false;
    }

    if (value.getCountry().length() < 4) {
      return false;
    }

    return true;
  }
}

Creating Custom Constraint Annotation

Now that we have the validator ready the below code shows how to create an annotation @Address for our address field in MyPerson class.


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;

//Linking the validator I had shown above.
@Constraint(validatedBy = {AddressValidator.class})
//This constraint annotation can be used only on fields and method parameters.
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(value = RetentionPolicy.RUNTIME)
@Documented
public @interface Address {

  //The message to return when the instance of MyAddress fails the validation.
  String message() default "Invalid Address";

  Class<?>[] groups() default {};
  Class<? extends Payload>[] payload() default {};
}

Test the New Custom Nonstraint using JUnit

Let’s try out the custom constraint you have created by now. For this I will create a JUnit test and then make use of javax.validation.Validator as I did in my previous post to simulate the validation of the data.


import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.BeforeClass;

public class MyPersonTest {

  public MyPersonTest() {
  }

  private static ValidatorFactory vf;
  private static Validator validator;
  private static MyAddress address;
  private static MyPerson person;

  /**
   * Setting up the validator and model data.
   */
  @BeforeClass
  public static void setup(){

    vf = Validation.buildDefaultValidatorFactory();
    validator = vf.getValidator();

    address = new MyAddress("#12, 4th Main", "XYZ Layout", "Bangalore", "Karnataka", "India", "560045");
    person = new MyPerson("First Name", "Last Name", address);

  }

  /**
   * Validating the model data which has correct values.
   */
  @Test
  public void testCorrectAddress(){
    System.out.println("********** Running validator with corret address **********");

    Set<ConstraintViolation<MyPerson>> violations = validator.validate(person);

    assertEquals(violations.size(), 0);
  }

  /**
   * Validating the model data which has incorrect values.
   */
  @Test
  public void testInvalidAddress(){
    System.out.println("********** Running validator against wrong address **********");

    //setting address itself as null
    person.setAddress(null);
    validateAndPrintErrors();

    //One of the address fields as null.
    address.setCity(null);
    person.setAddress(address);
    validateAndPrintErrors();

    //Setting pin code less than 6 characters.
    address.setPinCode("123");
    address.setCity("Bangalore");
    validateAndPrintErrors();

    //Setting country name with less  than 4 characters
    address.setPinCode("123456");
    address.setCountry("ABC");
    validateAndPrintErrors();

  }

  private void validateAndPrintErrors(){
    Set<ConstraintViolation<MyPerson>> violations = validator.validate(person);

    for ( ConstraintViolation<MyPerson> viol : violations){
      System.out.println(viol.getMessage());
    }
    assertEquals(1, violations.size());
  }
}

And the output will be:

********** Running validator with corret address **********
********** Running validator against wrong address **********
Invalid Address
Invalid Address
Invalid Address
Invalid Address

Comments

comments

About Mohamed Sanaulla

In his day job he works on developing enterprise applications using ADF. He is also the moderator of JavaRanch forums and an avid blogger.

Speak Your Mind

*

Close
Please support the site
By clicking any of these buttons you help our site to get better