Spring MVC: Multiple Row Form Submit using List of Beans

Recently I had a requirement where using Spring MVC we had to take inputs multiple rows of data from user. The form had many rows which user can edit and submit. Spring MVC provides very simple yet elegant way of collecting data from multiple rows from HTML form and store them in List of Beans in Java.

Lets look at the requirement first. We have a screen where data for multiple Contacts is displayed. The Contact data is displayed in an HTML table. Each row in the table represents a single contact. Contact details consist of attributes such as Firstname, Lastname, Email and Phone number.

Related: Spring 3 MVC Tutorial Series (Must Read)

The Add Contact form would look like following:
spring-mvc-multi-row-form

Lets see the code behind this example.

Tools and Technologies used:

  1. Java 5 or above
  2. Eclipse 3.3 or above
  3. Spring MVC 3.0

Step 1: Create Project Structure

Open Eclipse and create a Dynamic Web Project.
eclipse-dynamic-web-project

Enter project name as SpringMVC_Multi_Row and press Finish.

Step 2: Copy Required JAR files

Once the Dynamic Web Project is created in Eclipse, copy the required JAR files under WEB-INF/lib folder. Following are the list of JAR files:
spring-mvc-multi-row-jar-files


Step 3: Adding Spring MVC support

Once the basic project setup is done, we will add Spring 3 MVC support. For that first modify default web.xml and add springs DispatcherServlet.

File: /WebContent/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	id="WebApp_ID" version="2.5">
	<display-name>Spring3MVC-Multi-Row</display-name>
	<servlet>
		<servlet-name>spring</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>spring</servlet-name>
		<url-pattern>*.html</url-pattern>
	</servlet-mapping>
</web-app>

Related: Tutorial: Learn Spring MVC Lifecycle

Now add spring-servlet.xml file under WEB-INF folder.

File: /WebContent/WEB-INF/spring-servlet.xml

 
<?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.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
		http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
		
	<context:annotation-config />
	<context:component-scan base-package="net.viralpatel.spring3.controller" />	

	<bean id="jspViewResolver"
		class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="viewClass"
			value="org.springframework.web.servlet.view.JstlView" />
		<property name="prefix" value="/WEB-INF/jsp/" />
		<property name="suffix" value=".jsp" />
	</bean>
</beans>

Note that in above spring-servlet file, line 10, 11 defines context:annotation-config and component-scan tags. These tags let Spring MVC knows that the spring mvc annotations are used to map controllers and also the path from where the controller files needs to be loaded. All the files below package net.viralpatel.spring3.controller will be picked up and loaded by spring mvc.

Step 4: Add Spring Controller and Form classes

File: /src/net/viralpatel/spring3/form/Contact.java

package net.viralpatel.spring3.form;

public class Contact {
	private String firstname;
	private String lastname;
	private String email;
	private String phone;

	public Contact() {
	}

	public Contact(String firstname, String lastname, String email, String phone) {
		this.firstname = firstname;
		this.lastname = lastname;
		this.email = email;
		this.phone = phone;
	}
	
	// Getter and Setter methods
}

File: /src/net/viralpatel/spring3/form/ContactForm.java

package net.viralpatel.spring3.form;

import java.util.List;

public class ContactForm {

	private List<Contact> contacts;

	public List<Contact> getContacts() {
		return contacts;
	}

	public void setContacts(List<Contact> contacts) {
		this.contacts = contacts;
	}
}

Note line 7 in above code how we have defined a List of bean Contact which will hold the multi-row data for each Contact.

File: /src/net/viralpatel/spring3/controller/ContactController.java

package net.viralpatel.spring3.controller;

import java.util.ArrayList;
import java.util.List;

import net.viralpatel.spring3.form.Contact;
import net.viralpatel.spring3.form.ContactForm;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class ContactController {

	
	private static List<Contact> contacts = new ArrayList<Contact>();

	static {
		contacts.add(new Contact("Barack", "Obama", "barack.o@whitehouse.com", "147-852-965"));
		contacts.add(new Contact("George", "Bush", "george.b@whitehouse.com", "785-985-652"));
		contacts.add(new Contact("Bill", "Clinton", "bill.c@whitehouse.com", "236-587-412"));
		contacts.add(new Contact("Ronald", "Reagan", "ronald.r@whitehouse.com", "369-852-452"));
	}
	
	@RequestMapping(value = "/get", method = RequestMethod.GET)
	public ModelAndView get() {
		
		ContactForm contactForm = new ContactForm();
		contactForm.setContacts(contacts);
		
		return new ModelAndView("add_contact" , "contactForm", contactForm);
	}
	
	@RequestMapping(value = "/save", method = RequestMethod.POST)
	public ModelAndView save(@ModelAttribute("contactForm") ContactForm contactForm) {
		System.out.println(contactForm);
		System.out.println(contactForm.getContacts());
		List<Contact> contacts = contactForm.getContacts();
		
		if(null != contacts && contacts.size() > 0) {
			ContactController.contacts = contacts;
			for (Contact contact : contacts) {
				System.out.printf("%s \t %s \n", contact.getFirstname(), contact.getLastname());
			}
		}
		
		return new ModelAndView("show_contact", "contactForm", contactForm);
	}
}

In above ContactController class, we have defile two methods: get() and save().

get() method: This method is used to display Contact form with pre-populated values. Note we added a list of contacts (Contacts are initialize in static block) in ContactForm bean object and set this inside a ModelAndView object. The add_contact.jsp is displayed which in turns display all contacts in tabular form to edit.

save() method: This method is used to fetch contact data from the form submitted and save it in the static array. Also it renders show_contact.jsp file to display contacts in tabular form.

Step 5: Add JSP View files

Add following files under WebContent/WEB-INF/jsp/ directory.

File: /WebContent/WEB-INF/jsp/add_contact.jsp

<%@taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
<head>
	<title>Spring 3 MVC Multipe Row Submit - viralpatel.net</title>
</head>
<body>

<h2>Spring MVC Multiple Row Form Submit example</h2>
<form:form method="post" action="save.html" modelAttribute="contactForm">
	<table>
	<tr>
		<th>No.</th>
		<th>Name</th>
		<th>Lastname</th>
		<th>Email</th>
		<th>Phone</th>
	</tr>
	<c:forEach items="${contactForm.contacts}" var="contact" varStatus="status">
		<tr>
			<td align="center">${status.count}</td>
			<td><input name="contacts[${status.index}].firstname" value="${contact.firstname}"/></td>
			<td><input name="contacts[${status.index}].lastname" value="${contact.lastname}"/></td>
			<td><input name="contacts[${status.index}].email" value="${contact.email}"/></td>
			<td><input name="contacts[${status.index}].phone" value="${contact.phone}"/></td>
		</tr>
	</c:forEach>
</table>	
<br/>
<input type="submit" value="Save" />
	
</form:form>
</body>
</html>

In above JSP file, we display contact details in a table. Also each attribute is displayed in a textbox. Note that modelAttribute=”contactForm” is defined in <form:form /> tag. This tag defines the modelAttribute name for Spring mapping. On form submission, Spring will parse the values from request and fill the ContactForm bean and pass it to the controller.

Also note how we defined textboxes name. It is in form contacts[i].a. Thus Spring knows that we want to display the List item with index i and its attribute a.

contacts[${status.index}].firstname will generate each rows as follows:

contacts[0].firstname // mapped to first item in contacts list
contacts[1].firstname // mapped to second item in contacts list
contacts[2].firstname // mapped to third item in contacts list

Spring 3 MVC and path attribute and square bracket

One thing here is worth noting that we haven’t used Spring’s
tag to render textboxes. This is because Spring MVC 3 has a unique way of handling path attribute for
tag. If we define the textbox as follows:

<form:input path="contacts[${status.index}].firstname" />

Then instead of converting it to following HTML code:

<input name="contacts[0].firstname" />
<input name="contacts[1].firstname" />
<input name="contacts[2].firstname" />

It converts it into following:

<input name="contacts0.firstname" />
<input name="contacts1.firstname" />
<input name="contacts2.firstname" />

Note how it removed square brackets [ ] from name attribute. In previous versions of Spring (before 2.5) the square bracket were allowed in name attribute.

It seems w3c has later changed the HTML specification and removed [ ] from html input name.
Read the specification http://www.w3.org/TR/html4/types.html#type-name. It clearly says that:

ID and NAME tokens must begin with a letter ([A-Za-z]) and may be followed by any number of letters, digits ([0-9]), hyphens (“-”), underscores (“_”), colons (“:”), and periods (“.”).

Thus, square brackets aren’t allowed in name attribute! And thus Spring 3 onwards this was implemented.

So far I haven’t got any workaround to use springs <form:input /> tag instead of plain html <input /> to render and fetch data from multiple rows.

File: /WebContent/WEB-INF/jsp/show_contact.jsp

<%@taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
<head>
	<title>Spring 3 MVC Multipe Row Submit - viralpatel.net</title>
</head>
<body>
<h2>Show Contacts</h2>
<table width="50%">
	<tr>
		<th>Name</th>
		<th>Lastname</th>
		<th>Email</th>
		<th>Phone</th>
	</tr>
	<c:forEach items="${contactForm.contacts}" var="contact" varStatus="status">
		<tr>
			<td>${contact.firstname}</td>
			<td>${contact.lastname}</td>
			<td>${contact.email}</td>
			<td>${contact.phone}</td>
		</tr>
	</c:forEach>
</table>	
<br/>
<input type="button" value="Back" onclick="javascript:history.back()"/>
</body>
</html>

File: /WebContent/index.jsp

<jsp:forward page="get.html"></jsp:forward>


Final Project Structure

Once we have added all relevant source files and jar files, the project structure should look like following:
spring-multi-row-project-structure


Step 6: Execute it

Execute the web application Right click on project > Run As > Run on Server.

Add Contact page

Show Contact page
spring-multiple-row-list-show-page

Download Source Code

Spring-MVC-Multiple-Row-List-example.zip (2.9 MB)



76 Comments

  • Ganeshan 8 March, 2013, 13:46

    sorry my html elment does’t show up in my example post above

    Viral,

    Nice article. I have a problem around the same dynamic binding.
    I have a table with dynamically created rows using javascript.
    Read it in many sites that if the name is proper the binding will happen automatically.
    For example:

    In my case i’m doing the exact thing and it works for 1 row created statically like above.
    But if i add another row dynamically like this (array index 1) using javascript

    it doesn’t work. On the server side because of the lazy list i see the size is 2 but field itself is null. Please help. I’m almost going mad.

  • PRAMIL K PRINCE 8 March, 2013, 16:22

    I have a form that dynamically add text boxes and that are binded to list object in command object.
    The same form is used to retrieve the value form list.The form enable us to delete the textboxes ,which is binded to list in command object,but the list in command object is not reinitialized .What is the solution for this?

  • Mayank Patel 9 March, 2013, 8:38

    Hello Viral
    I always appreciate your tutorial and work,for most of java framework i have followed your tutorials at starting phase to accelerate my learning.Now i am working with one US Client project (confidential) .I stuck with spring MVC binding with some complex forms.
    The forms are getting populated with data and binding but when i submit the form its showing exception like contacts[0] invalid property of form bean….root : spring autogrow nested property path.
    i followed this tutorial to make every complex forms(multiple list.multiple radio,multiple checkbox)
    Please Reply

  • Firoz 11 March, 2013, 19:56

    Excellent explanation

  • Ramesh 15 March, 2013, 16:20

    Thanks a lot, very nice example.

  • Ninju Bohra 8 April, 2013, 18:17

    With the newer release of Spring MVC (I am using 3.1.1) you can use the {/code] way of expressing the input boxes (on the JSP page).

    Spring has apparently corrected the value generated in the name=”…” parameter so that it conforms to the w3c spec!

    Woo hoo!

  • Ninju Bohra 8 April, 2013, 18:35

    Hmm looks like the commenting did not come across properly.

    You can you use

       <form:input path="..." />
    

    as was a way to express the input fields…Spring MVC has updated their code to be w3c compliant

  • Sanjeev Rai 17 April, 2013, 19:04

    Hey, Please can you provide the example of webflow example with multiple controller

  • Saurabh Mehta 9 May, 2013, 17:39

    Hi,

    Here you populated the list before viewing it on “add_contact.jsp”. I have a requirement where user can add n number of objects at runtime from view layer.

    Please suggest.

    Thanks!!

    • Viral Patel 10 May, 2013, 14:43

      That should’nt be a problem. You can define a method in ContactController to add new users in List contacts. Map this new method using @RequestMapping(“/adduser”). Hope you got whats to be done here.

  • Jason 14 May, 2013, 9:43

    IMPORTANT
    Actually w3c spec is not saying “name” attribute in “input” element can’t have square bracket. The “NAME” is just its internal term to categorize attribute. See this and find “name” attribute for “input” element:
    http://www.w3.org/TR/html4/index/attributes.html
    The attribute type is CDATA! There is little restriction for CDATA type, see
    http://www.w3.org/TR/html4/types.html#type-cdata

  • Interesting 22 May, 2013, 7:21

    Nice example…btw, Spring 3.2.2 form:input works and will create the same hand-coded input.

    However, I noticed that on submission, Spring creates new instances of Contact and does not reuse the Contact instances that were populated in the ContactForm at form get time…

    I wonder if I set something up incorrectly.

  • Interesting 22 May, 2013, 7:53

    Following up on my observation: if I use @SessionAttribute(“myFormObject”) then it works fine…Spring 3.2.2

Leave a Reply

Your email address will not be published. Required fields are marked *

Note

To post source code in comment, use [code language] [/code] tag, for example:

  • [code java] Java source code here [/code]
  • [code html] HTML here [/code]