Friday, December 2, 2011

Bean Validation Made Simple With JSR 303

    JSR 303 (Bean Validation) is the specification of the Java API for JavaBean validation in Java EE and Java SE. Simply put it provides an easy way of ensuring that the properties of your JavaBean(s) have the right values in them. This post aims to show you how to use the Bean Validation API in your project.
    To begin, imagine that you were building the next Facebook and you would need member(s) to register to use your application. In order to successfully register, prospective member(s) have to provide the following: a last name, a first name, a gender, an email address and a date of birth. In addition, the individual who is registering must be between 18 and 150 years inclusive.
    Prior to JSR 303, you probably would have needed a bunch of if-else statements to achieve the above requirements. Thankfully, not any more.
    We will begin by creating a JavaBean named 'Member' that would hold all the properties we are interested in.
01 package validationapiblog.model;
02 
03 import java.util.Date;
04 import validationapiblog.enums.Gender;
05 
06 /**
07  *
08  @author Adedayo Ominiyi
09  */
10 public class Member {
11 
12     private String lastName = null;
13     private String firstName = null;
14     private Gender gender = null;
15     private String emailAddress = null;
16     private Date dateOfBirth = null;
17 
18     public Member() {
19     }
20 
21     public String getFirstName() {
22         return firstName;
23     }
24 
25     public void setFirstName(String firstName) {
26         this.firstName = firstName;
27     }
28 
29     public Gender getGender() {
30         return gender;
31     }
32 
33     public void setGender(Gender gender) {
34         this.gender = gender;
35     }
36 
37     public String getLastName() {
38         return lastName;
39     }
40 
41     public void setLastName(String lastName) {
42         this.lastName = lastName;
43     }
44 
45     public Date getDateOfBirth() {
46         return dateOfBirth;
47     }
48 
49     public void setDateOfBirth(Date dateOfBirth) {
50         this.dateOfBirth = dateOfBirth;
51     }
52 
53     public Integer getAge() {
54         if (this.dateOfBirth != null) {
55             // calculate age of member here
56         }
57         return null;
58     }
59 
60     public String getEmailAddress() {
61         return emailAddress;
62     }
63 
64     public void setEmailAddress(String emailAddress) {
65         this.emailAddress = emailAddress;
66     }
67 }
Java2html

The gender property is a simple enum and is shown below
1 package validationapiblog.enums;
2 
3 /**
4  *
5  @author Adedayo Ominiyi
6  */
7 public enum Gender {
8     MALE, FEMALE;
9 }
Java2html

Now that we have the pieces to the puzzle. The next step is to download an implementation of JSR 303. For this post we would be using the reference implementation namely Hibernate Validator. The version as at the time this post was written is 4.2.0 Final. After downloading it you should add the following 4 jars to the classpath of your project:
  • hibernate-validator-4.2.0.Final.jar
  • hibernate-validator-annotation-processor-4.2.0.Final.jar
  • slf4j-api-1.6.1.jar
  • validation-api-1.0.0.GA.jar
Once this is done, you simply annotate the 'Member' JavaBean we created earlier to indicate which properties need be validated. You can annotate either the fields or the accessor (or getter) methods of the JavaBean. In this post I will be annotating the accessor methods.

01 package validationapiblog.model;
02 
03 import java.util.Date;
04 import javax.validation.constraints.Max;
05 import javax.validation.constraints.Min;
06 import javax.validation.constraints.NotNull;
07 import javax.validation.constraints.Past;
08 import javax.validation.constraints.Pattern;
09 import org.hibernate.validator.constraints.Email;
10 import org.hibernate.validator.constraints.NotBlank;
11 import validationapiblog.enums.Gender;
12 
13 /**
14  *
15  @author Adedayo Ominiyi
16  */
17 public class Member {
18 
19     private String lastName = null;
20     private String firstName = null;
21     private Gender gender = null;
22     private String emailAddress = null;
23     private Date dateOfBirth = null;
24 
25     public Member() {
26     }
27 
28     @NotNull(message = "First name is compulsory")
29     @NotBlank(message = "First name is compulsory")
30     @Pattern(regexp = "[a-z-A-Z]*", message = "First name has invalid characters")
31     public String getFirstName() {
32         return firstName;
33     }
34 
35     public void setFirstName(String firstName) {
36         this.firstName = firstName;
37     }
38 
39     @NotNull(message = "Gender is compulsory")
40     public Gender getGender() {
41         return gender;
42     }
43 
44     public void setGender(Gender gender) {
45         this.gender = gender;
46     }
47 
48     @NotNull(message = "Last name is compulsory")
49     @NotBlank(message = "Last name is compulsory")
50     @Pattern(regexp = "[a-z-A-Z]*", message = "Last name has invalid characters")
51     public String getLastName() {
52         return lastName;
53     }
54 
55     public void setLastName(String lastName) {
56         this.lastName = lastName;
57     }
58 
59     @Past(message = "Date of Birth must be the past")
60     @NotNull
61     public Date getDateOfBirth() {
62         return dateOfBirth;
63     }
64 
65     public void setDateOfBirth(Date dateOfBirth) {
66         this.dateOfBirth = dateOfBirth;
67     }
68 
69     @Min(value = 18, message = "Age must be greater than or equal to 18")
70     @Max(value = 150, message = "Age must be less than or equal to 150")
71     public Integer getAge() {
72         if (this.dateOfBirth != null) {
73             // calculate age of member here
74         }
75         return null;
76     }
77 
78     @NotNull(message="Email Address is compulsory")
79     @NotBlank(message="Email Address is compulsory")
80     @Email(message = "Email Address is not a valid format")
81     public String getEmailAddress() {
82         return emailAddress;
83     }
84 
85     public void setEmailAddress(String emailAddress) {
86         this.emailAddress = emailAddress;
87     }
88 }
Java2html

Please note that these are just some of the annotations available in JSR 303. In addition Hibernate Validator introduces a few of its own that are not in the specification. Feel free to study the annotations not in this post in your free time you might find something interesting. There is also the ability to create your own custom validator if the need arises. Now lets review the annotations used:
  • @NotNull - Checks that the annotated value is not null. Unfortunately it doesn't check for empty string values
  • @Pattern - Checks if the annotated string matches the regular expression given. We used it to ensure that the last name and first name properties have valid string values
  • @Past - The annotated element must be a date in the past.
  • @Min - The annotated element must be a number whose value must be greater or equal to the specified minimum
  • @Max - The annotated element must be a number whose value must be lower or equal to the specified maximum
  • @NotBlank - Checks that the annotated string is not null and the trimmed length is greater than 0. This annotation is not in JSR 303
  • @Email - Checks whether the specified string is a valid email address. This annotation is also not in JSR 303
To test the validation we could use a unit test as shown below. 

package validationapiblog.test;

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

/**
 *
 @author Adedayo Ominiyi
 */
public class ValidationAPIUnitTest {

    public ValidationAPIUnitTest() {
    }

    @Test
    public void testMemberWithNoValues() {
        Member member = new Member();

        // validate the input
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();
        Set<ConstraintViolation<Member>> violations = validator.validate(member);
        assertEquals(violations.size()5);
    }
}
Java2html
In conclusion, you should experiment with JSR 303 and see for yourself which annotations you like. Thank you and have fun.