Use Java annotations to validate your Spring WebMVC form beans.
The Spring Bean Validation Framework, which is
part of the Spring Modules
project, allows you to perform validation declaratively using Java annotations.
I've always liked the declarative approach, which we saw for instance in
Commons Validator, but annotation-based validation is especially convenient.
JSR 303 (Bean Validation)
specifies some standards around bean validation, though the Spring Bean Validation
Framework does not adopt those standards. The Hibernate
Validator project, on the other hand, aims to provide an implementation of
the emerging JSR 303 standard.
While it very well could be subpar Googling skills on my part, there
doesn't seem to be much detailed how-to information out there on
actually using the Bean Validation Framework. Hence this article.
I'm using Spring 2.5.x (specifically, Spring 2.5.5) and Spring
Modules 0.9. I assume that you already know Spring and Spring WebMVC in
particular.
If you want to download the code, you can do so here:
You'll have to download the dependencies separately though.
Dependencies
Here's what you'll need (again, I'm using Spring 2.5.x and Spring Modules 0.9):
commons-collections.jar
commons-lang.jar
commons-logging.jar
spring.jar
spring-modules-validation.jar
spring-webmvc.jar
Java Sources
I'm going to do things a little differently than I normally do, and
start with the Java first. We're going to build a very simple "Contact
Us" form of the sort that you might use to ask a question, complain
about lousy service, or whatever. Since we're just showing how
validation works, I've left out the service and persistence tiers.
We're going to do everything with a form bean and a controller.
Here's the form bean:
Code listing:
contact.UserMessage
- package contact;
-
- import org.springmodules.validation.bean.conf.loader.annotation.handler.Email;
- import org.springmodules.validation.bean.conf.loader.annotation.handler.Length;
- import org.springmodules.validation.bean.conf.loader.annotation.handler.NotBlank;
-
- public final class UserMessage {
-
- @NotBlank
- @Length(max = 80)
- private String name;
-
- @NotBlank
- @Email
- @Length(max = 80)
- private String email;
-
- @NotBlank
- @Length(max = 4000)
- private String text;
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public String getEmail() {
- return email;
- }
-
- public void setEmail(String email) {
- this.email = email;
- }
-
- public String getText() {
- return text;
- }
-
- public void setText(String text) {
- this.text = text;
- }
- }
package contact;
import org.springmodules.validation.bean.conf.loader.annotation.handler.Email;
import org.springmodules.validation.bean.conf.loader.annotation.handler.Length;
import org.springmodules.validation.bean.conf.loader.annotation.handler.NotBlank;
public final class UserMessage {
@NotBlank
@Length(max = 80)
private String name;
@NotBlank
@Email
@Length(max = 80)
private String email;
@NotBlank
@Length(max = 4000)
private String text;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
The bean itself is pretty uninteresting—I have field for the user's
name, e-mail address, and the message text. But the cool part is that
I've included annotations that specify validation constraints. It's
probably self-explanatory, but I've specified that none of the fields
is allowed to be blank, and I've also specified the maximum lengths for
each. (You can also specify minimum lengths, which one could use
instead of @NotBlank
, but I'm using @NotBlank
instead for a reason I'll explain in just a bit.) Finally, I've specified that email
needs to be a valid e-mail address. It's that simple!
Here are the rest of the validation rules you can use.
Now here's the Spring MVC controller, which I've implemented as a POJO controller:
Code listing:
contact.ContactController
- package contact;
-
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Controller;
- import org.springframework.ui.ModelMap;
- import org.springframework.validation.BindingResult;
- import org.springframework.validation.Validator;
- import org.springframework.web.bind.annotation.ModelAttribute;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestMethod;
-
- @Controller
- public final class ContactController {
-
- @Autowired
- private Validator validator;
-
- public void setValidator(Validator validator) {
- this.validator = validator;
- }
-
- @RequestMapping(value = "/form", method = RequestMethod.GET)
- public ModelMap get() {
-
- // Because we're not specifying a logical view name, the
- // DispatcherServlet's DefaultRequestToViewNameTranslator kicks in.
- return new ModelMap("userMessage", new UserMessage());
- }
-
- @RequestMapping(value = "/form", method = RequestMethod.POST)
- public String post(@ModelAttribute("userMessage") UserMessage userMsg,
- BindingResult result) {
-
- validator.validate(userMsg, result);
- if (result.hasErrors()) { return "form"; }
-
- // Use the redirect-after-post pattern to reduce double-submits.
- return "redirect:thanks";
- }
-
- @RequestMapping("/thanks")
- public void thanks() {
- }
- }
package contact;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Validator;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public final class ContactController {
@Autowired
private Validator validator;
public void setValidator(Validator validator) {
this.validator = validator;
}
@RequestMapping(value = "/form", method = RequestMethod.GET)
public ModelMap get() {
// Because we're not specifying a logical view name, the
// DispatcherServlet's DefaultRequestToViewNameTranslator kicks in.
return new ModelMap("userMessage", new UserMessage());
}
@RequestMapping(value = "/form", method = RequestMethod.POST)
public String post(@ModelAttribute("userMessage") UserMessage userMsg,
BindingResult result) {
validator.validate(userMsg, result);
if (result.hasErrors()) { return "form"; }
// Use the redirect-after-post pattern to reduce double-submits.
return "redirect:thanks";
}
@RequestMapping("/thanks")
public void thanks() {
}
}
The Bean Validation Framework includes its own Validator
implementation, called BeanValidator
, and I'm making that injectable here. Also, note that we're going to autowire it in.
It may be that there's a standard, predefined interceptor to apply BeanValidator
(as opposed to injecting the Validator
into the controller), but if there is, I haven't seen it. I'd be interested to hear if you, gentle reader, know of one.
The noteworthy method here is the second post()
method, which contains the validation code. I just call the standard validate()
method, passing in the form bean and the BindingResult
,
and return the current logical view name if there's an error. That way
the form shows the validation error messages, which we'll see below. If
everything passes validation, I just redirect to a "thank you" page.
Now let's look at how we define the validation messages that the end user sees if his form submission fails validation.
Validation Messages
Code listing:
/WEB-INF/classes/errors.properties
- UserMessage.name[not.blank]=Please enter your name.
- UserMessage.name[length]=Please enter no more than {2} characters.
- UserMessage.email[not.blank]=Please enter your e-mail address.
- UserMessage.email[email]=Please enter a valid e-mail address.
- UserMessage.email[length]=Please enter no more than {2} characters.
- UserMessage.text[not.blank]=Please enter a message.
- UserMessage.text[length]=Please enter no more than {2} characters.
UserMessage.name[not.blank]=Please enter your name.
UserMessage.name[length]=Please enter no more than {2} characters.
UserMessage.email[not.blank]=Please enter your e-mail address.
UserMessage.email[email]=Please enter a valid e-mail address.
UserMessage.email[length]=Please enter no more than {2} characters.
UserMessage.text[not.blank]=Please enter a message.
UserMessage.text[length]=Please enter no more than {2} characters.
The keys should be fairly self-explanatory given UserMessage
above. Each key involves a class, a field and an annotation. The values
are parametrizable messages, not unlike Commons Validator messages if
you're familiar with those. In the three length
messages, I'm using {2}
to indicate argument #2—viz., max
—for the length
validation rule. Argument #1 happens to be min
,
and argument #0 in general appears to be the form bean itself. I can
imagine that it would be nice to be able to use the form bean to get at
the specific submitted value so you could say things like "You entered
4012 characters, but the limit is 4000 characters." And I think there's
actually a way to do that though I don't myself know how to do it yet.
(This is another one of those areas where I'd appreciate whatever
information you may have.)
I mentioned above that I chose @NotBlank
instead of @Length(min = 1, max = 80)
.
The reason is that I wanted to use a specific error message ("Please
enter your name") if the message is blank. I could have just used
"Please enter a name between 1-80 characters" but that sounds slightly
silly compared to "Please enter your name", and since I'm a usability
guy I care about such things.
The JSPs
We have two JSPs: the form itself, and a basic (really basic) "thank you" page.
Code listing:
/WEB-INF/form.jsp
- <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
-
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-
- <html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <title>Contact Us</title>
- <style>
- .form-item { margin: 20px 0; }
- .form-label { font-weight: bold; }
- .form-error-field { background-color: #FFC; }
- .form-error-message { font-weight: bold; color: #900; }
- </style>
- </head>
- <body>
-
- <h1>Contact Us</h1>
-
- <%-- Give command object a meaningful name instead of using default name, 'command' --%>
- <form:form commandName="userMessage">
- <div class="form-item">
- <div class="form-label">Your name:</div>
- <form:input path="name" size="40" cssErrorClass="form-error-field"/>
- <div class="form-error-message"><form:errors path="name"/></div>
- </div>
- <div class="form-item">
- <div class="form-label">Your e-mail address:</div>
- <form:input path="email" size="40" cssErrorClass="form-error-field"/>
- <div class="form-error-message"><form:errors path="email"/></div>
- </div>
- <div class="form-item">
- <div class="form-label">Your message:</div>
- <form:textarea path="text" rows="12" cols="60" cssErrorClass="form-error-field"/>
- <div class="form-error-message"><form:errors path="text"/></div>
- </div>
- <div class="form-item">
- <input type="submit" value="Submit" />
- </div>
- </form:form>
-
- </body>
- </html>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Contact Us</title>
<style>
.form-item { margin: 20px 0; }
.form-label { font-weight: bold; }
.form-error-field { background-color: #FFC; }
.form-error-message { font-weight: bold; color: #900; }
</style>
</head>
<body>
<h1>Contact Us</h1>
<%-- Give command object a meaningful name instead of using default name, 'command' --%>
<form:form commandName="userMessage">
<div class="form-item">
<div class="form-label">Your name:</div>
<form:input path="name" size="40" cssErrorClass="form-error-field"/>
<div class="form-error-message"><form:errors path="name"/></div>
</div>
<div class="form-item">
<div class="form-label">Your e-mail address:</div>
<form:input path="email" size="40" cssErrorClass="form-error-field"/>
<div class="form-error-message"><form:errors path="email"/></div>
</div>
<div class="form-item">
<div class="form-label">Your message:</div>
<form:textarea path="text" rows="12" cols="60" cssErrorClass="form-error-field"/>
<div class="form-error-message"><form:errors path="text"/></div>
</div>
<div class="form-item">
<input type="submit" value="Submit" />
</div>
</form:form>
</body>
</html>
This is just a standard Spring WebMVC form, so I'll invoke my "I assume you know Spring WebMVC" assumption here. The cssErrorClass
attribute is kind of fun if you don't already know about it. It
indicates the CSS class to use in the event of a validation error. You
can combine that with the cssClass
attribute (which applies in the non-error case) though I haven't done that here.
Now here's the basic "thank you" page:
Code listing:
/WEB-INF/thanks.jsp
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-
- <html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <title>Thank You</title>
- </head>
- <body>
- <h1>Thank You</h1>
- </body>
- </html>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Thank You</title>
</head>
<body>
<h1>Thank You</h1>
</body>
</html>
(I told you it was basic...)
OK, now we're ready to move onto application configuration. Almost done!
Servlet and Spring Configuration
Here's our completely standard web.xml
:
Code listing:
/WEB-INF/web.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <web-app 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"
- version="2.5">
-
- <servlet>
- <servlet-name>contact</servlet-name>
- <servlet-class>
- org.springframework.web.servlet.DispatcherServlet
- </servlet-class>
- </servlet>
-
- <servlet-mapping>
- <servlet-name>contact</servlet-name>
- <url-pattern>/contact/*</url-pattern>
- </servlet-mapping>
- </web-app>
<?xml version="1.0" encoding="UTF-8"?>
<web-app 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"
version="2.5">
<servlet>
<servlet-name>contact</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>contact</servlet-name>
<url-pattern>/contact/*</url-pattern>
</servlet-mapping>
</web-app>
Though I said I'm assuming you already know Spring WebMVC, I'll just
point out that since I didn't specify a custom location for the
application context file, I have to put it at /WEB-INF/contact-servlet.xml
. If you want the file to live elsewhere, or if you want it to be associated with the servlet context instead of the DispatcherServlet
, you'll have to set that up in web.xml
accordingly.
Here's the Spring application context:
Code listing:
/WEB-INF/contact-servlet.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:context="http://www.springframework.org/schema/context"
- xmlns:p="http://www.springframework.org/schema/p"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
- http://www.springframework.org/schema/context
- http://www.springframework.org/schema/context/spring-context-2.5.xsd">
-
- <!-- Enable annotation-based validation using Bean Validation Framework -->
- <!-- Using these instead of vld namespace to prevent Eclipse from complaining -->
- <bean id="configurationLoader"
- class="org.springmodules.validation.bean.conf.loader.annotation
- .AnnotationBeanValidationConfigurationLoader"/>
- <bean id="validator" class="org.springmodules.validation.bean.BeanValidator"
- p:configurationLoader-ref="configurationLoader"/>
-
- <!-- Load messages -->
- <bean id="messageSource"
- class="org.springframework.context.support.ResourceBundleMessageSource"
- p:basenames="errors"/>
-
- <!-- Discover POJO @Components -->
- <!-- These automatically register an AutowiredAnnotationBeanPostProcessor -->
- <context:component-scan base-package="contact"/>
-
- <!-- Map logical view names to physical views -->
- <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
- p:prefix="/WEB-INF/"
- p:suffix=".jsp"/>
- </beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<!-- Enable annotation-based validation using Bean Validation Framework -->
<!-- Using these instead of vld namespace to prevent Eclipse from complaining -->
<bean id="configurationLoader"
class="org.springmodules.validation.bean.conf.loader.annotation
.AnnotationBeanValidationConfigurationLoader"/>
<bean id="validator" class="org.springmodules.validation.bean.BeanValidator"
p:configurationLoader-ref="configurationLoader"/>
<!-- Load messages -->
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource"
p:basenames="errors"/>
<!-- Discover POJO @Components -->
<!-- These automatically register an AutowiredAnnotationBeanPostProcessor -->
<context:component-scan base-package="contact"/>
<!-- Map logical view names to physical views -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:prefix="/WEB-INF/"
p:suffix=".jsp"/>
</beans>
(IMPORTANT: In the configurationLoader
definition, make sure you put the class name on a single line. I had to break it up for formatting purposes.)
If you're not familiar with it, I'm using the p
namespace here for syntactic sugar—it allows me to specify properties using a nice shorthand.
The first two beans basically create the BeanValidator
instance we're going to use. It turns out that instead of defining
these two beans explicitly, you can use a special element from a
special namespace:
- namespace is
xmlns:vld="http://www.springmodules.org/validation/bean/validator"
;
- purported schema location is
http://www.springmodules.org/validation/bean/validator-2.0.xsd
;
- element is
<vld:annotation-based-validator id="validator"/>
But when I do it, Eclipse complains (even though the code works when
you run it) since there isn't at the time of this writing actually an
XSD at the specified location. (At least there's a JIRA ticket for it.) So I'll just use the two beans for now.
The other stuff is mostly normal Spring WebMVC stuff. I put the
message source on the app context, scan for the controller (which is
why I'm autowiring the validator into the controller), and put a view
resolver on the context too.
Finis
Build and deploy your WAR, and then point your browser to your web app; for example:
http://localhost:8080/contact-example/contact/form
Try submitting the form with empty fields, or an invalid e-mail
address, or fields that are too long. If things are working correctly,
you ought to see error messages and even field highlighting when
validation fails.
And that, my friends, is it! Feel free to post a comment if you run
into problems getting it to work and I'll try to help if I can.
Again, if you want to download the sample code (minus dependencies; see above), here it is:
Have fun!