Introduction to Spring Validation

March 1, 2011

Spring Framework

«»

Introduction

In this article, we will see the usage of Spring Validators. Spring provides a simplified set of APIs and supporting classes for validating domain objects. As always, the framework is extensible and it is possible to hook-in custom validator implementations into the framework. This article starts in explaining the basics of Spring validator by guiding to write a custom validators for simple and complex objects. Later on, it explores the theory behind ‘message codes resolver’ and illustrates the usage of writing customized version of the same. Finally, the article concludes in the usage of validator in the Web tier. This article assumes that readers has the sufficient knowledge on Spring Framework. If you are beginner looking for basic concepts on Spring Framework, please read Introduction to Spring Framework.

Download Spring Validation Sample Code

Validating Simple objects

In this section, we will see how to make use of Spring’s Validation services with the help of an example. At the core, Spring provides an interface called ‘Validator’ which can be implemented by the clients for validating custom objects. We will illustrate the usage of validation with the help of an example.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package net.javabeat.articles.validator.simple;
 
public class Book {
 
	private String name;
	private String author;
	private int noOfPages;
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		this.name = name;
	}
 
	public String getAuthor() {
		return author;
	}
 
	public void setAuthor(String author) {
		this.author = author;
	}
 
	public int getNoOfPages() {
		return noOfPages;
	}
 
	public void setNoOfPages(int noOfPages) {
		this.noOfPages = noOfPages;
	}	
}

The above code defines a model class called ‘Book’ which we want to validate. The class defines basic property like ‘name’, ‘author’ and the ‘noOfPages’. We wanted to ensure that the caller creating a book should set non-null values to the properties ‘name’ and ‘author’. For the property ‘noOfPages’, a value of zero or a negative value cannot be defined.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package net.javabeat.articles.validator.simple;
 
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
 
public class BookValidator implements Validator{
 
	@Override
	public boolean supports(Class<?> classObject) {
		return classObject == Book.class;
	}
 
	@Override
	public void validate(Object object, Errors errors) {
 
		Book book = (Book)object;
		if (book.getName() == null || book.getName().trim().length() == 0){			
			errors.rejectValue("name", "name.required", "Name field is missing");
		}
 
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "author", "author.required", "Author field is mandatory");
 
		int pages = book.getNoOfPages();
		if (pages <= 0){
			errors.rejectValue("noOfPages", "noOfPages.incorrect", "No of pages has an incorrect value (zero or -ve)");
		}
	}
}

Have a look at the above class which validates the book object. Note that the custom validator implements the interface ‘Validator’. The method ‘validate()’ can be invoked directly or indirectly by the framework/caller to invoke the validation. Note that the first argument passed to this method is the object to be validated and the second object is the ‘Errors’ object. This object is expected to be populated within the method in case of any errors.

For validating the ‘name’ property, we check if the value is null or empty and if so, we call the method ‘rejectValue()’ that is defined on the ‘errors’ object. The argument to this method is the name of the field that violated the validation, the error code and the error message. Note that there is a utility method ‘rejectIfEmptyOrWhitespace’ which is available in the class ‘ValidationUtils’ which does the job of ‘Errors.rejectValue’. We have used this method for validating the property ‘author’. We have also checked if the value for the property ‘noOfPages’ in case if the value is zero or negative.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package net.javabeat.articles.validator.simple;
 
import java.util.Arrays;
import java.util.List;
 
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.validation.ValidationUtils;
 
public class BookClient {
 
	public static void main(String[] args) {
 
		testNameEmpty();
		testNoOfPages();
	}
 
	private static void testNameEmpty(){
 
		BookValidator bookValidator = new BookValidator();		
		Book bookObject = new Book();	
		bookObject.setNoOfPages(10);
 
		BeanPropertyBindingResult result = new BeanPropertyBindingResult(bookObject, "book");		
		ValidationUtils.invokeValidator(bookValidator, bookObject, result);		
 
		System.out.println("Total error count is " + result.getErrorCount());
		System.out.println("-------------------------------------------------");
 
		List<ObjectError> allObjectErrors = result.getAllErrors();
		for (ObjectError objectError : allObjectErrors){
 
			if (objectError instanceof FieldError){
 
				FieldError fieldError = (FieldError)objectError;
				System.out.println("Field name is " + fieldError.getField());
			}
 
			System.out.println("Codes " + Arrays.asList(objectError.getCodes()).toString());
			System.out.println("Error Code is " + objectError.getCode());
			System.out.println("Default message is " + objectError.getDefaultMessage());
			System.out.println();
		}
		System.out.println("-------------------------------------------------");
	}
 
	private static void testNoOfPages(){
 
		BookValidator bookValidator = new BookValidator();		
		Book bookObject = new Book();
		bookObject.setName("Programming in Java");
		bookObject.setAuthor("Some Author");
 
		BeanPropertyBindingResult result = new BeanPropertyBindingResult(bookObject, "book");		
		ValidationUtils.invokeValidator(bookValidator, bookObject, result);		
 
		System.out.println("Total error count is " + result.getErrorCount());
		System.out.println("-------------------------------------------------");
 
		List<ObjectError> allObjectErrors = result.getAllErrors();
		for (ObjectError objectError : allObjectErrors){
 
			if (objectError instanceof FieldError){
 
				FieldError fieldError = (FieldError)objectError;
				System.out.println("Field name is " + fieldError.getField());
			}
 
			System.out.println("Error Code is " + objectError.getCode());
			System.out.println("Default message is " + objectError.getDefaultMessage());
			System.out.println();
		}
		System.out.println("-------------------------------------------------");
	}
}

Here is the client program that makes use of validation. In the first test method, we have created an instance of Book object and haven’t set the properties ‘name’ and ‘author’. For invoking the validator object on the book object we have used the utility method ‘invokeValidator’ defined on the class ‘ValidationUtils’ by passing the validator object, the object to be validated and the ‘errors’ object. In case of any validation errors, the ‘Errors’ object can be queried to obtain information on the same. In this case, we have used the method ‘getErrorCount()’ to get the number of validation errors. After that, an iteration is done to obtain the list of errors.

Validating Complex objects

In this section, we will see how to validate a complex object, i.e. an object containing custom type or a set of custom types as its properties. Let us take the definition of a model object ‘Product’ whose code listing is given below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package net.javabeat.articles.validator.complex;
 
import java.util.HashSet;
import java.util.Set;
 
public class Product {
 
	private String name;
	private String category;
	private String version;
	private LicenseType licenseType;
	private Set<Platform> supportedPlatforms;
 
	public Product(){
		supportedPlatforms = new HashSet<Platform>();
	}
 
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getCategory() {
		return category;
	}
	public void setCategory(String category) {
		this.category = category;
	}
	public String getVersion() {
		return version;
	}
	public void setVersion(String version) {
		this.version = version;
	}
	public Set<Platform> getSupportedPlatforms() {
		return supportedPlatforms;
	}
	public void setSupportedPlatforms(Set<Platform> supportedPlatforms) {
		this.supportedPlatforms = supportedPlatforms;
	}
 
	public LicenseType getLicenseType() {
		return licenseType;
	}
 
	public void setLicenseType(LicenseType licenseType) {
		this.licenseType = licenseType;
	}		
}

This model class defines the simple properties such as ‘name’, ‘category’ and ‘version’. It also defines a complex property ‘licenseType’ which is of type ‘LicenseType’. Since a software can be supported in a list of platforms, this model defines a property called ‘supportedPlatforms’ which is a set where each element in the set is of type ‘Platform’.

1
2
3
4
5
6
7
8
package net.javabeat.articles.validator.complex;
 
public enum LicenseType {
 
	FREEWARE,
 
	SHAREWARE
}</code>

Have a look at the definition for the enum type ‘LicenseType’. For simplicity we have defined two enumeration types which are ‘FREEWARE’ and ‘SHAREWARE’. The complete code listing for the type ‘Platform’ is given below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package net.javabeat.articles.validator.complex;
 
public class Platform {
 
	private String name;
 
	public Platform(String name){
		this.name = name;
	}
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		this.name = name;
	}	
}

The definition of the model ‘Platform’ is pretty straightforward. It has a property called ‘name’ which can take values such as ‘Windows XP’, ‘Windows Vista’, ‘Unix’ etc. The subsequent sections show the corresponding validator classes for the model classes ‘LicenseType’, ‘Platform’ and ‘Product’.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package net.javabeat.articles.validator.complex;
 
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
 
public class LicenseTypeValidator implements Validator{
 
	@Override
	public boolean supports(Class<?> classObject) {
		return LicenseType.class == classObject;
	}
 
	@Override
	public void validate(Object target, Errors errors) {
 
		LicenseType licenseType = (LicenseType)target;
		if (licenseType == null){
			errors.reject("licenseType.null", "License type is null");
		}		
	}
}

As we can see in the above section, the validation logic for the ‘licenseType’ property is simple as we have ensured that we cannot pass a ‘null’ licenseType. For validating the platform object whose listing is given below, we have asserted that the ‘platform name’ cannot be null or empty. If so, we have used the ‘reject()’ method to populate the error condition on the ‘name’ field.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package net.javabeat.articles.validator.complex;
 
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
 
public class PlatformValidator implements Validator{
 
	@Override
	public boolean supports(Class<?> classObject) {
		return Platform.class == classObject;
	}
 
	@Override
	public void validate(Object target, Errors errors) {
 
		Platform platform = (Platform)target;
		if (platform == null){
			errors.reject("platform.null", "Platform object is null");
		}
 
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "name.required");		
	}
 
}

Have a close look at the validator class for the Platform model. For validating the platform object, we have to do validation on the primitive properties of the platform object as well as the custom or the complex properties we have defined. Hence we have defined the ‘licenset-type-validator’ and the ‘platform-validator’ as part of ‘product-validator’.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package net.javabeat.articles.validator.complex;
 
import java.util.Set;
 
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
 
public class ProductValidator implements Validator{
 
	private LicenseTypeValidator licenseTypeValidator;
	private PlatformValidator platformValidator;
 
	public void setLicenseTypeValidator(LicenseTypeValidator licenseTypeValidator){
		this.licenseTypeValidator = licenseTypeValidator;
	}
 
	public void setPlatformValidator(PlatformValidator platformValidator){
		this.platformValidator = platformValidator;
	}
 
	@Override
	public boolean supports(Class<?> classObject) {
		return classObject == Product.class;
	}
 
	@Override
	public void validate(Object object, Errors errors) {
 
		Product product = (Product)object;
		if (product == null){
			errors.reject("product.null", "Product is null");
		}
 
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "name.required", "Name is required");
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "category", "category.required", "Category is required");
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "version", "version.required", "Version is required");
 
		// For License type
		try{
			ValidationUtils.invokeValidator(licenseTypeValidator, product.getLicenseType(), errors);
		}catch (Exception e){
			e.printStackTrace();
		}		
 
		// For platforms
		Set<Platform> allPlatforms = product.getSupportedPlatforms();
		if (allPlatforms == null || allPlatforms.size() == 0){
			ValidationUtils.rejectIfEmptyOrWhitespace(errors, "supportedPlatforms", "supportedPlatforms.required", "Platforms is required");
		}else{
			try{
				for (Platform platform : allPlatforms){
					ValidationUtils.invokeValidator(platformValidator, platform, errors);
				}
			}catch (Exception exception){
				exception.printStackTrace();
			}
		}		
	}
}

In the above code, after validating the primitive properties of the product object such as its ‘name’, ‘category’ and ‘version’, we have delegated the control to ‘license-validator’ for validating the ‘licenseType’ property by making use of ValidationUtils. Then, we have iterated over the list of platforms and for each platform object we have invoked the ‘platform-validator’ object for performing validation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package net.javabeat.articles.validator.complex;
 
import java.util.Arrays;
import java.util.List;
 
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.validation.ValidationUtils;
 
public class ProductClient {
 
	public static void main(String[] args) {
 
		testSuccess();		
		testLicenseTypeNull();
		testPlatforms();
	}
 
	private static void testSuccess(){
 
		Product javaProduct = new Product();	
		javaProduct.setName("Java");
		javaProduct.setCategory("Programming Language");
		javaProduct.setVersion("7.0");
		javaProduct.setLicenseType(LicenseType.SHAREWARE);
 
		Platform windowsPlatform = new Platform("Windows/XP");
		javaProduct.getSupportedPlatforms().add(windowsPlatform);
 
		Platform unixPlatform = new Platform("Unix");
		javaProduct.getSupportedPlatforms().add(unixPlatform);
 
		validate(javaProduct);
	}
 
	private static void testLicenseTypeNull(){
 
		Product javaProduct = new Product();	
		javaProduct.setName("Java");
		javaProduct.setCategory("Programming Language");
		javaProduct.setVersion("7.0");
 
		Platform windowsPlatform = new Platform("Windows/XP");
		javaProduct.getSupportedPlatforms().add(windowsPlatform);
 
		Platform unixPlatform = new Platform("Unix");
		javaProduct.getSupportedPlatforms().add(unixPlatform);
 
		validate(javaProduct);
	}
 
	private static void testPlatforms(){
 
		Product javaProduct = new Product();	
		javaProduct.setName("Java");
		javaProduct.setCategory("Programming Language");
		javaProduct.setVersion("7.0");
		javaProduct.setLicenseType(LicenseType.FREEWARE);
 
		Platform windowsPlatform = new Platform("Windows/XP");
		javaProduct.getSupportedPlatforms().add(windowsPlatform);
 
		Platform nullPlatform = new Platform(null);
		javaProduct.getSupportedPlatforms().add(nullPlatform);
 
		validate(javaProduct);
	}
 
	private static void validate(Product javaProduct){
 
		ProductValidator productValidator = new ProductValidator();	
		productValidator.setLicenseTypeValidator(new LicenseTypeValidator());
		productValidator.setPlatformValidator(new PlatformValidator());		
 
		BeanPropertyBindingResult result = new BeanPropertyBindingResult(javaProduct, "javaProduct");		
		ValidationUtils.invokeValidator(productValidator, javaProduct, result);		
 
		System.out.println("Total error count is " + result.getErrorCount());
		System.out.println("-------------------------------------------------");
 
		List<ObjectError> allObjectErrors = result.getAllErrors();
		for (ObjectError objectError : allObjectErrors){
 
			if (objectError instanceof FieldError){
 
				FieldError fieldError = (FieldError)objectError;
				System.out.println("Field name is " + fieldError.getField());
			}
 
			System.out.println("Codes " + Arrays.asList(objectError.getCodes()).toString());
			System.out.println("Error Code is " + objectError.getCode());
			System.out.println("Default message is " + objectError.getDefaultMessage());
			System.out.println();
		}
		System.out.println("-------------------------------------------------");
	}
}

In the above client code, we have test for a successful scenario by populating the required and the desired values to the product object in the method ‘testSuccess’. In the ‘testLicenseTypeNull’ method, we have set ‘null’ reference to the ‘licenseType’ property and we have checked whether invoking validation on the product object has returned any errors. Similarly in the ‘testPlatforms()’ method, we have set ‘null’ platforms if the validation has actually got fired on the platform objects.

 

email

«»

Comments

comments

  • Prash

    Thank you.