Home > Solutions Log, Spring > JSR 303 Bean Validation Using Spring 3

JSR 303 Bean Validation Using Spring 3

The JSR 303 specification provides a metadata model for JavaBean validation. The validation API provides a variety of annotations that makes validation easy at any layer. In this post I will demo Spring 3′s support for JSR 303 in the web layer. You can download the code here (eclipse project).

The first part of the post talks about creating a simple bookstore admin application using Spring MVC. The second part of the post will add JSR 303 validation. If you are an experienced Spring developer, you can safely skip to the second part. The final part will show how to customize the validation error messages.

1. Online Bookstore Admin Application

The admin application will be designed to allow administrators enter information about new books. Here are the steps that implement this requirement:

Step 1. Create a web project and add all the required jars
I will be using Eclipse’s and here is a list of the required jars that need to be in the classpath:

org.springframework.asm-3.0.1.RELEASE.jar
org.springframework.beans-3.0.1.RELEASE.jar
org.springframework.context-3.0.1.RELEASE.jar
org.springframework.context.support-3.0.1.RELEASE.jar
org.springframework.core-3.0.1.RELEASE.jar
org.springframework.expression-3.0.1.RELEASE.jar
org.springframework.test-3.0.1.RELEASE.jar
org.springframework.web-3.0.1.RELEASE.jar
org.springframework.web.servlet-3.0.1.RELEASE.jar
commons-collections-3.1.jar
hibernate-validator-4.0.2.GA.jar
jcl-over-slf4j-1.5.10.jar
joda-time-1.6.jar
jstl-1.2.jar
log4j.jar
slf4j-api-1.5.8.jar
slf4j-log4j12.jar
validation-api-1.0.0.GA.jar

Step 2. Create a web-Context.xml file under WEB-INF folder to hold web layer spring bean configuration. Here are the contents of the file:

		
		<?xml version="1.0" encoding="UTF-8"?>
		<beans xmlns="http://www.springframework.org/schema/beans"
			xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
			xmlns:context="http://www.springframework.org/schema/context"
			xmlns:mvc="http://www.springframework.org/schema/mvc"
			xsi:schemaLocation="
				http://www.springframework.org/schema/beans	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
				http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
				http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
			
			<!-- Enable annotation driven controllers, validation etc... -->
			<mvc:annotation-driven />
			
			<!-- Controllers package -->
			<context:component-scan base-package="com.inflinx.blog.bookstore.web.controller" />
		
			<!-- JSP page location -->
			<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
				<property name="suffix" value=".jsp"/>
			</bean>
			 	
		</beans>
			
	

Step 3. Add Spring MVC Dispatcher Servlet to web.xml file. In the declaration below, Spring MVC will handle all URL requests to html pages.

		
		<?xml version="1.0" encoding="UTF-8"?>
		<web-app version="2.5" 
			xmlns="http://java.sun.com/xml/ns/javaee" 
			xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
			xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 

http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

		  <servlet>
				<servlet-name>bookstore</servlet-name>
				<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
				<init-param>
					<param-name>contextConfigLocation</param-name>
					<param-value>/WEB-INF/web-Context.xml</param-value>
				</init-param>
				<load-on-startup>1</load-on-startup>
			</servlet>
		
			<servlet-mapping>
				<servlet-name>bookstore</servlet-name>
				<url-pattern>*.html</url-pattern>
			</servlet-mapping>
		
		</web-app>
		
	

Step 4. Create a Book domain object to hold information about a book:

		package com.inflinx.blog.bookstore.domain;

		public class Book
		{
			private String name;
			private String description;

			public String getName()
			{
				return name;
			}
			public void setName(String name)
			{
				this.name = name;
			}
			public String getDescription()
			{
				return description;
			}
			public void setDescription(String description)
			{
				this.description = description;
			}

			@Override
			public String toString()
			{
				return "Name: " + name + ", Description: " + description ;
			}
		}	
	

Step 5. Create a BookFormController to prepare the form and process form submission

		package com.inflinx.blog.bookstore.web.controller;
		
		import java.util.Map;
		
		import org.springframework.stereotype.Controller;
		import org.springframework.web.bind.annotation.RequestMapping;
		import org.springframework.web.bind.annotation.RequestMethod;
		
		import com.inflinx.blog.bookstore.domain.Book;
		
		@Controller
		@RequestMapping("/book.html")
		public class BookFormController
		{
			// Display the form on the get request
			@RequestMapping(method=RequestMethod.GET)
			public String showForm(Map model)
			{
				Book book  = new Book();
				model.put("book", book);
				return "form";
			}
			
		}
	
	

Step 6. Create form.jsp page with a form to capture book details:

	
		<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
		<html>
			<head></head>
			<body>
				<form:form method="post" action="book.html" commandName="book">
					<table>
						<tr>
							<td>Name:</td> <td><form:input path="name" /></td>
						</tr> 
						<tr>
							<td>Description:</td> <td><form:textarea path="description"/></td>
						</tr>
						</table>
					<input type="submit" value="Create" />
				</form:form>
			</body>
		</html>
	
	
	

The form will be submitted to the book.html page.

Step 7. Modify BookFormController to read the submitted data and process it.

		
		// Process the form. 
		@RequestMapping(method=RequestMethod.POST)
		public String processForm(Book book, Map model)
		{
			// Persistence logic to save the book will go here

			// Add the saved book to the model
			model.put("book", book);
			return "result";
		}	
	

Step 8. Create a result.jsp page to display the form submission result:

		<html>
			<body>
				${book.name} is stored in the database
			</body>
		</html>
	

Deploy the application and go to http://<yourserver:port>/bookstore/book.html. The page should present a form shown below:

Form

Form

Enter the book name “Test Book” and submit the form. You should see a screen that looks similar to image below:

Form Submission Result

Form Submission Result

 

2 Adding JSR 303 validation

Consider the following validation requirements to the book class:
a. Name and Description cannot be null or blank
b. Description cannot be more than 50 characters

Here are the steps to add the above validation requirements:

Step 1. Add @NotEmpty and @Size annotations to the name and description files of the Book class

		public class Book
		{
			@NotEmpty
			private String name;
			
			@Size(min=1, max=50)
			private String description;
			
			// Getters and setters here	
		}
	

NotEmpty annotation is not part of JSR 303 specification. It is part of Hibernate Validator framework which is the reference implementation for JSR 303.

Step 2. Modify BookFormController to have Spring validate the submitted data. This is done by adding a @Valid annotation before the book method parameter. The result of the validation can be accessed using the BindingResult method parameter.

		// Process the form. 
		@RequestMapping(method=RequestMethod.POST)
		public String processForm(@Valid Book book, BindingResult result, Map model)
		{
			if(result.hasErrors())
			{
				return "form";
			}
			// Persistence logic to save the book will go here

			// Add the saved book to the model
			model.put("book", book);
			return "result";
		} 	
 	

Step 3. Modify the form.jsp so that error messages can be displayed:

 		<form:form method="post" action="book.html" commandName="book">
					<table>
						<tr>
							<td>Name:</td> <td><form:input path="name" /></td> <td><form:errors path="name" /></td>
						</tr> 
						<tr>
							<td>Description:</td> <td><form:textarea path="description"/></td> <td><form:errors path="description" /></td>
						</tr>
						</table>
					<input type="submit" value="Create" />
		</form:form>
 	

Redeploy the application and when you submit an empty form, you should see validation failure messages next to the fields:

Validation Failed

Validation Failed

 

3 Customizing error messages

The validation failure messages in the image above are default to the framework. To make custom error messages appear follow these steps:

Step 1. Create a messages.properties file under the WEB-INF folder. Add the following messages:

   		NotEmpty.book.name=Name is a required field
		Size.book.description=Description must be between 1 and 50 characters
   	

Each message above follows this convention:

<CONSTRAINT_NAME>.<COMMAND_NAME>.<FIELD_NAME>

Step 2. Let Spring know about the newly created properties file. This is done by modifying the web-Context.xml file to include this bean declaration:

   	
			
	
    

Redeploy the application and submit an empty form. This time you should see custom error messages appear next to the fields:

Custom Validation Failed Messages

Custom Validation Failed Messages

 

Download the source code for this post here. The application is tested on Glassfish 3.0 and WebLogic 10.3.

 

  1. Ion
    April 14th, 2010 at 05:06 | #1

    Thanks , very good article !

  2. Alexey
    April 21st, 2010 at 04:10 | #2

    What about ajax validation?

  3. April 21st, 2010 at 07:59 | #3

    @Alexey
    Looks like RichFaces has AJAX components that ties with Hibernate validator: http://docs.jboss.org/richfaces/latest_3_3_X/en/devguide/html/rich_ajaxValidator.html

  4. Jaakko
    May 9th, 2010 at 22:30 | #4

    Apparently this validation with annotations does not work with Spring MVCP Portlets? (Using latest 3.0.2 version)

  5. Jonathan
    May 21st, 2010 at 04:49 | #5

    I had to add the annotation

    @ModelAttribute(“book”)

    to the paramater list for the processForm method of the controller class to get validation working properly as follows :

    // Process the form.
    @RequestMapping(method=RequestMethod.POST)
    public String processForm(@Valid @ModelAttribute(“book”) Book book, BindingResult result, Map model)
    { etc….

    I am using Spring 3.0.2, Hibernate Validator 4.02

    Otherwise, I was getting the following exception :

    org.apache.jasper.JasperException: java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name ‘book’ available as request attribute
    org.apache.jasper.servlet.JspServletWrapper.handleJspException(JspServletWrapper.java:522)
    org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:410)
    org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:342)
    org.apache.jasper.servlet.JspServlet.service(JspServlet.java:267)

  6. Anjan
    May 26th, 2010 at 01:31 | #6

    cool… brief but explanatory… thanks

  7. June 13th, 2010 at 13:35 | #7

    Thanks, but what if you want to display the incorrect field values when you return back to the form (if there are validation errors)? In your controller’s showForm() method, you instantiate a Book object which wipes out the values that the user already submitted.

  8. August 10th, 2010 at 07:50 | #8

    @Alexey

    Hi,

    GraniteDS 2.2 comes with an ActionScript3/Flex (not Ajax, I know) implementation of the JSR-303 specification. Furthermore, its code generation tools may be configured to automatically replicate Java bean constraint annotation into the generated AS3 model. Entity validation may therefore be performed in the Flex client application the same way it will be perform on server side before persistence.

    See documentation here: http://www.graniteds.org/confluence/display/DOC22/3.+Validation+Framework+%28JSR-303+like%29.

    FW.

  9. Shrinivas Kini
    August 12th, 2010 at 00:06 | #9

    @Jonathan

    Ensure that you dont have spring-modules-validation.jar in the class pathc. Then you ensure that the @Valid annotation is referenced from the implementation in validation-api-*.jar of hibernate. This works for me. I have used @Valid annotation with and without JSR303 support.

  10. August 12th, 2010 at 16:55 | #10

    Nice tuto!!! Tks a lot

  11. Ksp
    September 22nd, 2010 at 01:21 | #11

    Thx Jonathan for adding your comment.
    @ModelAttribute(“book”) is required in my case as well.

  12. October 15th, 2010 at 01:46 | #12

    Good Article, Thanks.
    Spring is fantastic :)

  13. christophe13600
    October 28th, 2010 at 00:19 | #13

    @Jaakko
    Hello Jaakko i’m facing the same problems regarding validation with annotations does not work with Spring MVCP Portlets. I m using weblogic 10.2.3 do you have found a work around about this ?

  14. Ratzzak
    November 11th, 2010 at 00:05 | #14

    What if book had a reference to an Author object.

    Public Class Book{
    @NotEmpty
    private String name;

    @Size(min=1, max=50)
    private String description;

    private Author author;
    //Getters and Setters
    }

    Public Class Author{
    @NotEmpty
    private String authorName;

    //Getters and Setters
    }

    How would u validate this??

  15. November 19th, 2010 at 22:24 | #15

    great tutorial. i also wanna share a very good source of spring validation technique. you can find it here:

    http://www.adobocode.com/spring/adding-validation-to-spring-forms

    hope you’ll learn from it too.

  16. Tatha
    December 30th, 2010 at 04:35 | #16

    HI,

    Thanks for this article it has really helped to get the Valdiation up and running . I have tried running the code in both Apache Tomcat Server 6.0 and Oracle Weblogic 10.3.3 , It works fine Tomcat but in Weblogic its giving the following exception ..Does anyone have any idea the reason behind the same

    org.springframework.web.util.NestedServletException: Handler processing failed; nested exception is java.lang.AbstractMethodError
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:820)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:716)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:647)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:563)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
    at weblogic.servlet.internal.StubSecurityHelper$ServletServiceAction.run(StubSecurityHelper.java:227)
    at weblogic.servlet.internal.StubSecurityHelper.invokeServlet(StubSecurityHelper.java:125)
    at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:300)
    at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:183)
    at weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction.doIt(WebAppServletContext.java:3686)
    at weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction.run(WebAppServletContext.java:3650)
    at weblogic.security.acl.internal.AuthenticatedSubject.doAs(AuthenticatedSubject.java:321)
    at weblogic.security.service.SecurityManager.runAs(SecurityManager.java:121)
    at weblogic.servlet.internal.WebAppServletContext.securedExecute(WebAppServletContext.java:2268)
    at weblogic.servlet.internal.WebAppServletContext.execute(WebAppServletContext.java:2174)
    at weblogic.servlet.internal.ServletRequestImpl.run(ServletRequestImpl.java:1446)
    at weblogic.work.ExecuteThread.execute(ExecuteThread.java:201)
    at weblogic.work.ExecuteThread.run(ExecuteThread.java:173)
    Caused by: java.lang.AbstractMethodError
    at javax.persistence.Persistence$1.isLoaded(Persistence.java:78)
    at org.hibernate.validator.engine.resolver.JPATraversableResolver.isReachable(JPATraversableResolver.java:33)
    at org.hibernate.validator.engine.resolver.DefaultTraversableResolver.isReachable(DefaultTraversableResolver.java:112)
    at org.hibernate.validator.engine.resolver.SingleThreadCachedTraversableResolver.isReachable(SingleThreadCachedTraversableResolver.java:47)
    at org.hibernate.validator.engine.ValidatorImpl.isValidationRequired(ValidatorImpl.java:764)
    at org.hibernate.validator.engine.ValidatorImpl.validateConstraint(ValidatorImpl.java:331)
    at org.hibernate.validator.engine.ValidatorImpl.validateConstraintsForRedefinedDefaultGroup(ValidatorImpl.java:278)
    at org.hibernate.validator.engine.ValidatorImpl.validateConstraintsForCurrentGroup(ValidatorImpl.java:260)
    at org.hibernate.validator.engine.ValidatorImpl.validateInContext(ValidatorImpl.java:213)
    at org.hibernate.validator.engine.ValidatorImpl.validate(ValidatorImpl.java:119)
    at org.springframework.validation.beanvalidation.SpringValidatorAdapter.validate(SpringValidatorAdapter.java:74)
    at org.springframework.validation.DataBinder.validate(DataBinder.java:688)
    at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.doBind(HandlerMethodInvoker.java:746)
    at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.resolveHandlerArguments(HandlerMethodInvoker.java:296)
    at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.invokeHandlerMethod(HandlerMethodInvoker.java:163)
    at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod(AnnotationMethodHandlerAdapter.java:414)
    at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:402)
    at org.spri

  17. February 7th, 2011 at 11:00 | #17

    Hi

    Great tutorial, kept nice and simple. I got it working after I created a clean project in STS using the Spring Template Project option. I tried manually but something must have been missing or wrong version.

    Any way, after getting it working I thought I’d extend it a bit and learn some more…

    I added @Email private String email; with getter and setter to Book.java and added the form:input and form:errors for email to form.jsp.

    I expected it to work but it did NOT.

    So to check I had understood the steps in this tutorial I added another property: isbn.

    That worked!

    So, there is a problem with validating an @Email annotated field.

    Any ideas why this should be so?

  18. February 7th, 2011 at 11:18 | #18

    Hi

    Sorry for above, blank email does not get validated. When I entered ‘sadasd’ as the email it did get flagged as invalid.

    I used a @NotEmpty to cover email not entered.

    Cool.

  19. vi
    March 3rd, 2011 at 03:32 | #19

    HI~
    excuse me~ I have a question of chinese trad in properties file .
    I used NotEmpty.book.name=請 ,I can display book.jsp is ‘?’.
    Do you have anyone solution support, thank a lot.

  20. March 20th, 2011 at 23:17 | #20

    hi,thank you for this article. i am suck at English.

    i can’t use spring’s taglib
    how would i show error message to my JSP page ?

    JSTL helps ?

  21. rodel hidalgo
    April 15th, 2011 at 07:24 | #21

    hi,thanks for this tutorial. It’s great. but i have a problem like this:
    javax.validation.UnexpectedTypeException: No validator could be found for type: java.lang.Integer

    can you help me about this problem?
    thanks.

  22. Reyes Yang
    April 24th, 2011 at 01:30 | #22

    @vi
    I think you have to run:
    native2ascii -encoding utf-8 source_properties_file dest_properties_file
    Then make sure you web page’s encoding is same as utf-8, I think you can get the result you want now.
    Of course, utf-8 just a example, you can use any other Simple Chinese encoding you want.

  23. April 24th, 2011 at 02:15 | #23

    Thx very much for your nice article. That’s really what I need.

  24. Maximilian Eberl
    May 22nd, 2011 at 03:50 | #24

    Fine!
    Works out of the box.
    (Many other onlne examples do not!)
    Works also with the most recent dependencies:
    spring-XXX 3.0.5.RELEASE
    hibernate-validator 4.2.0.Beta2
    Hint: Use commons-collections version 2.1 because
    it is already referenced in one of the dependencies
    Have a nice and peaceful weekend!!

  25. July 11th, 2011 at 06:45 | #25

    It is really nice article for beginners to the point . Thanks dude

  26. Sudhir
    April 20th, 2012 at 02:55 | #26

    It is really nice article.

    Note: Don’t use “redirect:/form” in return from error.

    Ex: if(result.hasErrors())
    {
    return “redirect:form”;
    }
    If you used above code then validation will not work.

    Once again Thanks for such a good article.

  27. Binh Thanh Nguyen
    July 14th, 2013 at 20:05 | #27

    Thanks, it’s useful to me

  1. July 7th, 2010 at 23:25 | #1
  2. April 24th, 2012 at 20:41 | #2