One of the most commonly expected behaviors of any web application is that it should validate user data. Every time a user submits data into our web application, it needs to be validated. This is to prevent security attacks, wrong data, or simple user errors. We don't have control over what the user may type while submitting data into our web application. For example, they may type some text instead of a date, they may forget to fill a mandatory field, or suppose we used 12-character lengths for a field in the database and the user entered 15-character length data, then the data cannot be saved in the database. Similarly, there are lots of ways a user can feed incorrect data into our web application. If we accept those values as valid, then it will create errors and bugs when we process such inputs. This chapter will explain the basics of setting up validation with Spring MVC.
After finishing this chapter, you will have a clear idea about the following:
Java Bean Validation (JSR-303) is a Java specification that allows us to express validation constraints on objects via annotations. It allows the APIs to validate and report violations. Hibernate Validator is the reference implementation of the Bean Validation specification. We are going to use Hibernate Validator for validation.
You can see the available Bean Validation annotations at the following site:
https://docs.oracle.com/javaee/7/tutorial/bean-validation001.htm .
In this section, we will see how to validate a form submission in a Spring MVC application. In our project, we have the Add new product form already. Now let's add some validation to that form:
pom.xml
, which you will find under the root directory of the actual project.pom.xml
file. Select the Dependencies tab and click on the Add button of the Dependencies section.org.hibernate
, Artifact Id as hibernate-validator
, Version as 5.2.4.Final
, select Scope as compile, click the OK button, and save pom.xml
.Product
domain class and add the @Pattern
(javax.validation.constraints.Pattern
) annotation at the top of the productId
field, as follows: @Pattern(regexp="P[1-9]+", message="
{Pattern.Product.productId.validation}")
private String productId;
@Size
, @Min
, @Digits
, and @NotNull
(javax.validation.constraints.*
) annotations on the top of the name
and unitPrice
fields respectively, as follows:@Size(min=4, max=50, message=" {Size.Product.name.validation}") private String name; @Min(value=0, message=" {Min.Product.unitPrice.validation}") @Digits(integer=8, fraction=2, message=" {Digits.Product.unitPrice.validation}") @NotNull(message= " {NotNull.Product.unitPrice.validation}") private BigDecimal unitPrice;
messages.properties
from /src/main/resources
in your project and add the following entries into it:Pattern.Product.productId.validation = Invalid product ID. It should start with character P followed by number. Size.Product.name.validation = Invalid product name. It should be minimum 4 characters to maximum 50 characters long. Min.Product.unitPrice.validation = Unit price is Invalid. It cannot have negative values. Digits.Product.unitPrice.validation = Unit price is Invalid.It can have maximum of 2 digit fraction and 8 digit integer. NotNull.Product.unitPrice.validation = Unit price is Invalid. It cannot be empty. Min.Product.unitPrice.validation = Unit price is Invalid. It cannot be negative value.
ProductController
class and change the processAddNewProductForm
request mapping method by adding a @Valid
(javax.validation.Valid
) annotation in front of the newProduct
parameter. After finishing that, your processAddNewProductForm
method signature should look as follows:public String processAddNewProductForm(@ModelAttribute("newProduct") @Valid Product newProduct, BindingResult result, HttpServletRequest request)
processAddNewProductForm
method, add the following condition as the first statement:if(result.hasErrors()) { return "addProduct"; }
addProduct.jsp
from src/main/webapp/WEB-INF/views/
in your project and add the <form:errors>
tag for the productId
, name
, and unitPrice
input elements. For example, the product ID input tag will have the <form:errors>
tag beside it, as follows: <form:input id="productId" path="productId" type="text"
class="form:input-large"/>
<form:errors path="productId" cssClass="text-danger"/>
Remember, the path
attribute value should be the same as the corresponding input
tag.
<form:errors>
tag within the <form:form>
tag, as follows:<form:errors path="*" cssClass="alert alert-danger" element="div"/>
LocalValidatorFactoryBean
in our web application context configuration file WebApplicationContextConfig.java,
as follows:@Bean(name = "validator") public LocalValidatorFactoryBean validator() { LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean(); bean.setValidationMessageSource(messageSource()); return bean; }
getValidator
method in WebApplicationContextConfig
to configure our validator bean as the default validator, as follows:@Override public Validator getValidator(){ return validator(); }
http://localhost:8080/webstore/market/products/add
. We will see a webpage showing a web form to add product info. Without entering any values in the form, simply click on the Add button. You will see validation messages at the top of the form, as shown in the following screenshot:Since we decided to use the Bean Validation (JSR-303) specification, we needed an implementation of the Bean Validation specification, and we decided to use a Hibernate Validator implementation in our project; thus we need to add that JAR to our project as a dependency. That's what we did in steps 1 through 3.
From steps 4 and 5, we added some javax.validation.constraints
annotations such as @Pattern
, @Size
, @Min
, @Digits
, and @NotNull
on our domain class (Product.java
) fields. Using these annotations, we can define validation constraints on fields. There are more validation constraint annotations available under the javax.validation.constraints
package. Just for demonstration purposes, we have used a couple of annotations; you can check out the Bean Validation documentation for all available lists of constraints.
For example, take the @Pattern
annotation on top of the productId
field; it will check whether the given value for the field matches the regular expression that is specified in the regexp
attribute of the @Pattern
annotation. In our example, we just enforce that the value given for the productId
field should start with the character P
and should be followed by digits:
@Pattern(regexp="P[1-9]+", message=" {Pattern.Product.productId.validation}")
private String productId;
The message
attribute of every validation annotation is just acting as a key to the actual message from the message source file (messages.properties
). In our case, we specified Pattern.Product.productId.validation
as the key, so we need to define the actual validation message in the message source file. That's why we added some message entries in step 6. If you noticed the corresponding value for the key Pattern.Product.productId.validation
in the messages.properties
file, you will notice the following value:
Pattern.Product.productId.validation = Invalid product ID. It should start with character P followed by number.
You can even add localized error messages in the corresponding message source file if you want. For example, if you want to show error messages in Dutch, simply add error message entries in the messages_nl.properties
file as well. During validation, this message source will be automatically picked up by Spring MVC based on the chosen locale.
We defined the validation constraints in our domain object, and also defined the validation error messages in our message source file. What else do we need to do? We need to tell our controller to validate the form submission request. We did that through steps 7 and 8 in the processAddNewProductForm
method:
@RequestMapping(value = "/products/add", method = RequestMethod.POST)
public String processAddNewProductForm(@ModelAttribute("newProduct") @Valid Product newProduct, BindingResult result, HttpServletRequest request) {
if(result.hasErrors()) {
return "addProduct";
}
String[] suppressedFields = result.getSuppressedFields();
if (suppressedFields.length > 0) {
throw new RuntimeException("Attempting to bind disallowed fields: " + StringUtils.arrayToCommaDelimitedString(suppressedFields));
}
MultipartFile productImage = newProduct.getProductImage();
String rootDirectory = request.getSession().getServletContext().getRealPath("/");
if (productImage!=null && !productImage.isEmpty()) {
try {
productImage.transferTo(new File(rootDirectory+"resources\images"+ newProduct.getProductId() + ".png"));
} catch (Exception e) {
throw new RuntimeException("Product Image saving failed", e);
}
}
productService.addProduct(newProduct);
return "redirect:/market/products";
}
We first annotated our method parameter newProduct
with the @Valid
(javax.validation.Valid
) annotation. By doing so, Spring MVC will use the Bean Validation framework to validate the newProduct
object. As you already know, the newProduct
object is our form-backed bean. After validating the incoming form bean (newProduct
), Spring MVC will store the results in the result
object; this is again another method parameter of our processAddNewProductForm
method.
In step 8 we simply checked whether the result
object contains any errors. If so, we redirected to the same Add new product page; otherwise we proceeded to add productToBeAdded
to our repository.
So far everything is fine. At first we defined the constraints on our domain object and defined the error messages in the message source file (messages.properties
). Later we validated and checked the validation result in the controller's form processing method (processAddNewProductForm
), but we haven't said how to show the error messages in the view file. Here comes Spring MVC's special <form:errors>
tag to the rescue.
We added this tag for the productId
, name,
and unitPrice
input elements in step 9. If any of the input fields failed during validation, the corresponding error message will be picked up by this <form:errors>
tag:
<form:errors path="productId" cssClass="text-danger"/>
The path
attribute is used to identify the field in the form bean to look for errors, and the cssClass
attribute will be used to style the error message. I have used Bootstrap's style class text-danger
, but you can use any valid CSS- style class that you prefer to apply on the error message.
Similarly, in step 10, we have added a global <form:errors>
tag to show all error messages as a consolidated view at the top of the form:
<form:errors path="*" cssClass="alert alert-danger" element="div"/>
Here we have used the "*
" symbol for the path
attribute, which that means we want to show all the errors and element attributes, just indicating which type of element Spring MVC should use to list all the errors.
So far, we have done all the coding-related stuff that is needed to enable validation, but we have to do one final configuration in our web application context to enable validation; that is we need to introduce the Bean Validation framework to our Spring MVC. In steps 11 and 12 we did just that. We have defined a bean for LocalValidatorFactoryBean
(org.springframework.validation.beanvalidation.LocalValidatorFactoryBean
). This LocalValidatorFactoryBean
will initiate the Hibernate Validator during the booting of our application:
@Bean(name = "validator") public LocalValidatorFactoryBean validator() { LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean(); bean.setValidationMessageSource(messageSource()); return bean; }
The setValidationMessageSource
method of LocalValidatorFactoryBean
indicates which message source bean it should look to for error messages. Since, in Chapter 6, Internalize Your Store with Interceptor, we already configured message sources in our web application context, we just use that bean, which is why we assigned the value messageSource()
as the value for the setValidationMessageSource
method. You will see a bean definition under the method messageSource()
already in our web application context.
And finally, we introduced our validator
bean to Spring MVC by overriding the getValidator
method:
@Override public Validator getValidator(){ return validator(); }
That is all we did to enable validation; now, if you run our application and bring the Add new product page using the http://localhost:8080/webstore/market/products/add
URL, you can see the empty form ready to submit. If you submit that form without filling in any information, you will see error messages in red.
I have just added validation for the first three fields in the Product
domain class; you can extend the validation for the remaining fields. And try to add localisedlocalized error messages for the validation you are defining.
Here are some hints you can try out:
category
field is emptyunitsInStock
field to validate that the minimum number of Units in stock allowed is zero