Integrating Spring MVC 3.0 with JSR 303 (aka javax.validation.*)

Annotated POJO validation comes to a JDK near you!

The new annotated validation spec (jsr 303) is pretty slick, especially when used along side Spring MVC 3.0, and when backed by ejb3 entities. I’m pretty impressed with how easily it integrates with Spring MVC’s framework, and with how seamlessly error messages are passed to the form taglibs so they show up in your web forms.

I know some of you might argue that the current validation framework might not address complex validations, but after giving Hibernate’s reference implementation documentation a look, it seems interdependent validations are at least possible through embedded @Valid in complex objects. Even if you have to come up with your own really weird validation for a particular field, jsr 303/hibernate offers a way to create your own custom annotation driven validations. For the remaining 95% of all the other web forms, you’re probably going to be alright if you use the pre-defined validations offered by jsr 303.

Getting started

Download the jsr 303 reference implementation jars from SourceForge, via Hibernate’s download page. You’ll need to add the main, Hibernate validator jar (currently hibernate-validator-4.0.2.GA.jar as of 2/8/2009) and the included jars in the release jar’s lib directory to your application’s classpath if they’re not already there (if you’re on jboss 5.1, probably at least validation-api-1.0.0.GA.jar, maybe more). The Hibernate reference implementation release also includes the jar files required to run in a jdk 5 runtime, include those if you’re not running on jdk 6. Download Spring MVC from Spring’s download page, its part of the Spring 3.0 release. From what I can tell Spring MVC requires the following jars in your classpath:

Now that we’ve squared that away, on with the examples:

Example ejb3 jsr-303 validation equipped entity bean

import javax.persistence.*;

@Entity
@Table(name="tb_inventory")
public class InventoryItem implements Serializable {

	private static final long serialVersionUID = 1L;

	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	protected int id;

	@NotNull
	protected BigDecimal price = new BigDecimal("0.00");	

	@NotEmpty(message = "Name is a required field")
	protected String name;

	@Min(100)
	protected int minimumPrice;

	@Email
	private String mail;

	@Pattern(regexp="[a-z]+")
	private String lowerCaseName;

	@Future
	private Date futureDate;

	@AssertTrue
	private boolean mustBeTrue;

	@OneToMany
	@JoinTable(name="tb_order_items_to_images")
	@Valid
	protected List<InventoryImage> images = new ArrayList<InventoryImage>();

	// setters/getters here...
}

Lets examine the annotations:

@NotNull

@NotNull flags the annotated field as valid only if it has a value assigned. A String with a null value will fail, while a String with a “” value will pass. Since the “message” parameter is not defined, the error message will default to whatever the validation package ships with. In this case, I think the message will read “name is a required field”.

@NotEmpty(message = “Name is a required field”)

@NotEmpty flags the annotated field as valid if the field is both not null and not a value of “”. Since the “message” parameter is defined for this annotation, the param value will replace the default message passed into spring mvc’s error framework.

@Min(100)

@Min flags the field as valid only if it has a value equal to or higher than the value in the parens. The contrary to this is @Max, which will flag as valid values lower than the value in the parens.

@Email

@Email flags the field as valid only if the field is a valid email.

@Pattern(regexp=”[a-z]+”)

@Pattern will flag the field as valid only if the string matches the regular expression passed in as the parameter. In this case it will only pass if the string is made up only of lowercase letters.

@Future

@Future will flag as valid only if the date annotated is in the future. The contrary to this is @Past, which would be valid only if the date has already passed.

@AssertTrue

@AssertTrue will flag as valid if the annotated boolean resolves to true. The contrary to this is @AssertFalse, which will flag as valid only if the boolean resolves to false.

@Valid

@Valid will validate only if the complex object annotated validates as true. Lets say in this case that InventoryImage has two validation annotated fields; if any InventoryImage fails either of those two fields, then the enclosing InventoryItem will fail validation because of the @Valid annotation. This is how compelx cross object validations are supported, other than defining your own.

Now that we’ve annotated our bean, we’ll need to hook it into a Spring MVC controller.

The Spring MVC Controller

package com.faceroller.web;

@Controller
@RequestMapping("/inventory")
public class InventoryController {

	private static Log log = LogFactory.getLog(InventoryController.class);

	/**
	 * initialize the form
	 *
	 */
	@RequestMapping("/add/setup.go")
	public ModelAndView addInventorySetup(InventoryItem item){

		log.info("setup add inventory");
		return new ModelAndView("/inventory/add.jsp", "item", item);
	}

	/**
	 * process the form
	 *
	 */
	@RequestMapping(value="/add/meta.go", method=RequestMethod.POST)
	public String processInventoryMeta(
			@ModelAttribute("item") @Valid InventoryItem item,
			BindingResult result) {

		log.info("process add inventory");

		if (result.hasErrors()) {
			return "/inventory/add.jsp";
		}

		InventoryService service = ServiceLocator.getInventoryService();
		service.addInventoryItem(item);

		return "redirect:/add/images/"+item.getId();
	}

	/**
	 * forward to whatever page you want
	 *
	 */
	@RequestMapping("/browse/item/{itemId}")
	public ModelAndView getInventoryItem(@PathVariable int itemId){

		log.info("getting item");

		InventoryService service = ServiceLocator.getInventoryService();
		InventoryItem item = service.getInventoryItemById(itemId);

		return new ModelAndView("/inventory/browse.item.jsp", "item", item);
	}

}

Pay special attention to

	@RequestMapping(value = "/add/meta.go", method=RequestMethod.POST)
	public String processInventoryMeta(
			@ModelAttribute("item") @Valid InventoryItem item,
			BindingResult result)

You’ll notice @Valid marked up right before the InventoryItem item bean parameter. This is the annotation that does all the validation magic for us. There is no need to implement a custom validator factory, as spring mvc’s framework would normally require. If the bean fails validation, BindingResult result will be prepopulated with all corresponding JSR 303 validation errors. The catch is you have to add the @ModelAttribute(“item”) annotation to the signature, otherwise the form bean in the jsp will not have access to all the error messages passed along by the validations.

The jsp code

<form:form method="post" commandName="item" action="/process/form">
<table width="100%" border="0">
	<tr><td colspan="3" class="bottomPadding">
		<span class="secionHeader">Add item to inventory</span>
	</td></tr>
	<tr><td class="labelColumn" width="100">
		Price
	</td><td width="100">
		<form:input path="price"/>
	</td><td>
		<form:errors path="price" cssClass="error"/>
	</td></tr>
	<tr><td class="labelColumn">
		Name
	</td><td>
		<form:input path="name"/>
	</td><td>
		<form:errors path="name" cssClass="error"/>
	</td></tr>
</table>
</form:form>

This is just a simple form, nothing new here, but I’m including for completeness. The Spring MVC framework will correctly populate the form tags with any bean errors should the form fail validation. The form tags are part of the standard spring taglibs, found in the org.springframework.web.servlet.* jar included in the Spring 3.0 distribution.

Resources
Hibernate 4.x Validation reference implementation
Spring MVC 3.0 documentation



 
  • Twitter
  • del.icio.us
  • Reddit
  • Technorati
  • Google Bookmarks
  • Blogplay
  • Yahoo! Buzz
  • LinkedIn
  • Facebook

Related posts:

  1. Java, XML and XStream
  2. Ejb3 Basics: Deploying Message Driven Beans
  3. Ejb3 Basics: Bean Managed Transactions
Posted on February 8, 2010 at 12:35 am by Ant · Permalink
In: Java · Tagged with: , , , , ,

7 Responses

  1. craig Written by craig
    on February 12, 2010 at 6:41 am
    Permalink

    nice, thanks.

    [Reply]

  2. uberVU - social comments Written by uberVU - social comments
    on February 14, 2010 at 3:07 pm
    Permalink

    Social comments and analytics for this post…

    This post was mentioned on Twitter by gridlockd: “Spring MVC 3.0 and JSR 303 (aka javax.validation.*)” http://is.gd/8d88u #Java #Spring…

  3. [...] Technobabble » Spring MVC 3.0 and JSR 303 (aka javax.validation.*) – The new annotated validation spec (jsr 303) is pretty slick, especially when used along side Spring MVC 3.0, and when backed by ejb3 entities. I’m pretty impressed with how easily it integrates with Spring MVC’s framework, and with how seamlessly error messages are passed to the form taglibs so they show up in your web forms. [...]

  4. Todd Written by Todd
    on February 15, 2010 at 4:28 am
    Permalink

    Thanks for the example. It’s great. Just an FYI, I couldn’t get this to work on Google App Engine until after I upgraded to 1.3.1. Not sure if it was user error, but my error messages started popping up after that.

    [Reply]

    Ant

    Ant Reply:

    Hi Todd, thanks for the comment. Yeah – it seems that Google Apps Engine does something funky with form beans as of spring 2.5.x behind closed doors. I found this google support thread that discusses that specific problem in more detail. I can’t speak with authority but it would appear from your experience that GAE 1.3.1 might address it. I’m glad it wound up working for you, I really like how the two mesh together.

    [Reply]

  5. Karthik Written by Karthik
    on February 17, 2010 at 7:55 pm
    Permalink

    I see an issue with Spring MVC using @Valid annotation though since you cannot specify the JSR 303 validation ‘group’ that you want to validate. Consider the case of a wizard like flow where the same model object is populated across several steps. Specifying @Valid will trigger the validation on default group and some of the fields might not be relevant to a particular step in the wizard like workflow. and I don’t want to use Spring web flow :)

    [Reply]

    Ant

    Ant Reply:

    That’s an interesting case. I’m going to try and look at this and see what I can come up with.

    [Reply]

Subscribe to comments via RSS

Leave a Reply