Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

In the first article of this series we introduced the use of the form tag library and how to bind data to a controller.

In this article, we’ll cover the various tags that Spring MVC provides to help us create and validate forms.

2. The input Tag

We’ll get started with the input tag. This tag renders an HTML input tag using the bound value and type=’text’ by default:

<form:input path="name" />

Starting with Spring 3.1 you can use other HTML5-specific types, such as email, date, and others. For example, if we wanted to create an email field, we can use type=’email’:

<form:input type="email" path="email" />

Similarly, to create a date field, we can use type=’date’, which will render a date picker in many browsers compatible with HTML5:

<form:input type="date" path="dateOfBirth" />

3. The password Tag

This tag renders an HTML input tag with type=’password’ using the bound value. This HTML input masks the value typed into the field:

<form:password path="password" />

4. The textarea Tag

This tag renders an HTML textarea:

<form:textarea path="notes" rows="3" cols="20"/>

We can specify the number of rows and columns in the same way we would an HTML textarea.

5. The checkbox and checkboxes Tag

The checkbox tag renders an HTML input tag with type=’checkbox’. Spring MVC’s form tag library provides different approaches to the checkbox tag which should meet all our checkbox needs:

<form:checkbox path="receiveNewsletter" />

The above example generate a classic single checkbox, with a boolean value. If we set the bound value to true, this checkbox will be checked by default.

The following example generates multiple checkboxes. In this case, the checkbox values are hard-coded inside the JSP page:

Bird watching: <form:checkbox path="hobbies" value="Bird watching"/>
Astronomy: <form:checkbox path="hobbies" value="Astronomy"/>
Snowboarding: <form:checkbox path="hobbies" value="Snowboarding"/>

Here, the bound value is of type array or java.util.Collection:

String[] hobbies;

The purpose of the checkboxes tag is used to render multiple checkboxes, where the checkbox values are generated at runtime:

<form:checkboxes items="${favouriteLanguageItem}" path="favouriteLanguage" />

To generate the values we pass in an Array, a List or a Map containing the available options in the items property. We can initialize our values inside the controller:

List<String> favouriteLanguageItem = new ArrayList<String>();
favouriteLanguageItem.add("Java");
favouriteLanguageItem.add("C++");
favouriteLanguageItem.add("Perl");

Typically the bound property is a collection so it can hold multiple values selected by the user:

List<String> favouriteLanguage;

6. The radiobutton and radiobuttons Tag

This tag renders an HTML input tag with type=’radio’:

Male: <form:radiobutton path="sex" value="M"/>
Female: <form:radiobutton path="sex" value="F"/>

A typical usage pattern will involve multiple tag instances with different values bound to the same property:

private String sex;

Just like the checkboxes tag, the radiobuttons tag renders multiple HTML input tags with type=’radio’:

<form:radiobuttons items="${jobItem}" path="job" />

In this case, we might want to pass in the available options as an Array, a List or a Map containing the available options in the items property:

List<String> jobItem = new ArrayList<String>();
jobItem.add("Full time");
jobItem.add("Part time");

7. The select Tag

This tag renders an HTML select element:

<form:select path="country" items="${countryItems}" />

To generate the values we pass in an Array, a List or a Map containing the available options in the items property. Once again, we can initialize our values inside the controller:

Map<String, String> countryItems = new LinkedHashMap<String, String>();
countryItems.put("US", "United States");
countryItems.put("IT", "Italy");
countryItems.put("UK", "United Kingdom");
countryItems.put("FR", "France");

The select tag also support the use of nested option and options tags.

While the option tag renders a single HTML option, the options tag renders a list of HTML option tags.

The options tag takes an Array, a List or a Map containing the available options in the items property, just like the select tag:

<form:select path="book">
    <form:option value="-" label="--Please Select--"/>
    <form:options items="${books}" />
</form:select>

When we have the need to select several items at once, we can create a multiple list box. To render this type of list, just add the multiple=”true” attribute in the select tag.

<form:select path="fruit" items="${fruit}" multiple="true"/>

Here the bound property is an array or a java.util.Collection:

List<String> fruit;

8. The hidden Tag

This tag renders an HTML input tag with type=’hidden’ using the bound value:

<form:hidden path="id" value="12345" />

9. The Errors Tag

Field error messages are generated by validators associated with the controller. We can use The Errors Tag to render those field error messages:

<form:errors path="name" cssClass="error" />

This will display errors for the field specified in the path property. The error messages are rendered within a span tag by default, with .errors appended to the path value as the id, and optionally a CSS class from the cssClass property, which can be used to style the output:

<span id="name.errors" class="error">Name is required!</span>

To enclose the error messages with a different element instead of the default span tag, we can specify the preferred element inside the element attribute:

<form:errors path="name" cssClass="error" element="div" />

This renders the error messages within a div element:

<div id="name.errors" class="error">Name is required!</div>

In addition to having the capability to show errors for a specific input element, we can display the entire list of errors (regardless of field) for a given page. This is achieved by the use of the wildcard *:

<form:errors path="*" />

9.1. The Validator

To display errors for a given field we need to define a validator:

public class PersonValidator implements Validator {

    @Override
    public boolean supports(Class clazz) {
        return Person.class.isAssignableFrom(clazz);
    }

    @Override
    public void validate(Object obj, Errors errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "required.name");
    }
}

In this case, if the field name is empty, the validator returns the error message identified by required.name from the resource bundle.

The resource bundle is defined in the Spring XML configuration file as follows:

<bean class="org.springframework.context.support.ResourceBundleMessageSource" id="messageSource">
     <property name="basename" value="messages" />
</bean>

Or in a pure Java configuration style:

@Bean
public MessageSource messageSource() {
    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    messageSource.setBasenames("messages");
    return messageSource;
}

The error message is defined inside the messages.properties file:

required.name = Name is required!

To apply this validation, we need to include a reference to the validator in our controller and call the method validate in the controller method which is called when user submits the form:

@RequestMapping(value = "/addPerson", method = RequestMethod.POST)
public String submit(
  @ModelAttribute("person") Person person, 
  BindingResult result, 
  ModelMap modelMap) {

    validator.validate(person, result);

    if (result.hasErrors()) {
        return "personForm";
    }
    
    modelMap.addAttribute("person", person);
    return "personView";
}

9.2. JSR 303 Bean Validation

Starting from Spring 3, we can use JSR 303 (via the @Valid annotation) for bean validation. To do this we need a JSR303 validator framework on the classpath. We will use the Hibernate Validator (the reference implementation). Following is the dependency that we need to include in the POM:

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>8.0.1.Final</version>
</dependency>

To make Spring MVC support JSR 303 validation via the @Valid annotation, we need to enable the following in our Spring configuration file:

<mvc:annotation-driven/>

Or use the corresponding annotation @EnableWebMvc in a Java configuration:

@EnableWebMvc
@Configuration
public class ClientWebConfigJava implements WebMvcConfigurer {
    // All web configuration will go here
}

Next, we need to annotate the controller method that we want to validate with the @Valid annotation:

@RequestMapping(value = "/addPerson", method = RequestMethod.POST)
public String submit(
  @Valid @ModelAttribute("person") Person person, 
  BindingResult result, 
  ModelMap modelMap) {
 
    if(result.hasErrors()) {
        return "personForm";
    }
     
    modelMap.addAttribute("person", person);
    return "personView";
}

Now we can annotate the entity’s property to validate it with Hibernate validator annotation:

@NotEmpty
private String password;

By default, this annotation will display “may not be empty” if we leave the password input field empty.

We can override the default error message by creating a property in the resource bundle defined in the validator example. The key of the message follows the rule AnnotationName.entity.fieldname:

NotEmpty.person.password = Password is required!

10. Conclusion

In this tutorial we explored the various tags that Spring provides for working with forms.

We also had a look at the tag for validation error displaying and the configuration needed to display custom error messages.

All the examples above can be found in a GitHub project. This is an Eclipse-based project, so it should be easy to import and run as it is.

When the project runs locally, the form example can be accessed at:

http://localhost:8080/spring-mvc-xml/person

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.