Form Data Binding

Form data binding automatically copies the values typed into an HTML form into the fields of a Java object.It is used so you don’t read each field by hand with request.getParameter(). Spring creates the object, fills it, and converts text to numbers and dates.In Spring MVC it works through @ModelAttribute on the controller and th:object / th:field in the Thymeleaf page.The form field name must match the Java property name (name, age, dateOfBirth). Spring matches them and calls the setters.

Features

  • One object receives the whole form (@ModelAttribute Student student).
  • Text is converted to the right Java type automatically ("20" becomes Integer, "2004-05-12" becomes LocalDate).
  • Several values with the same name become a List (checkboxes).
  • th:field keeps the form and the object in sync, so values are pre-filled and remembered.
  • @InitBinder can block fields from being bound (here id, so a form can’t set the database id).
  • The bound object can be saved straight to MySQL.

Form Data Binding Types

Type How it works Example in this project
Object binding (@ModelAttribute) The whole form fills one Java object. register(@ModelAttribute Student student)
Single-field binding (@RequestParam) Each field is read into its own parameter. /students/quick-register
Single-value controls Text, radio buttons and drop-downs each bind to one String. name, gender, department
Multi-value binding Checkboxes with the same name bind to a List<String>. courses
Type-converted binding Text is converted to numbers and dates. age (Integer), dateOfBirth (LocalDate)

Hands-on Experiment: Creating a spring boot application with HTML form fields are mapped to Java objects in Spring applications.

The following steps are involved in developing this hands-on experiment.

Step 1: Generate the Project

Step 2: Project Structure

Step 3: pom.xml

Step 4: application.properties

Step 5: Create Entity Class

Step 6: Create Repository Interface

Step 7: Create Service Class

Step 8: Create Controller Class

Step 9: Main Application Class

Step 10: Run the Main Application Class

Step 1: Configure the Project on Spring Initializr

Go to start.spring.io and set:

  • Project: Maven
  • Language: Java
  • Spring Boot version: 3.2.x
  • Group: com.example
  • Artifact: formbinding
  • Name: formbinding
  • Package name: com.example.formbinding
  • Packaging: Jar
  • Java: 17
  • Dependencies: Spring Web, Spring Data JPA, Thymeleaf, MySQL Driver

Click Generate to download the ZIP, then extract and import it into your IDE as a Maven project.

Step 2: Generated Project Structure

Step 3: Explore pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>student-form-binding-demo</artifactId>
    <version>1.0.0</version>
    <name>student-form-binding-demo</name>

    <properties>
        <java.version>17</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Step 4: application.properties

spring.application.name=student-form-binding-demo
server.port=8082

spring.datasource.url=jdbc:mysql://localhost:3306/student_form_db?createDatabaseIfNotExist=true&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect

spring.thymeleaf.cache=falseCode language: JavaScript (javascript)

Step 5: Create Entity Class

package com.example.formbinding.entity;

import jakarta.persistence.CollectionTable;
import jakarta.persistence.Column;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.Table;
import org.springframework.format.annotation.DateTimeFormat;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;

// This single class is the Java model the form is bound to (and the database table).
@Entity
@Table(name = "students")
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;                 // text input      -> String

    private String email;                // email input     -> String

    private Integer age;                 // number input    -> Integer (type conversion)

    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    @Column(name = "date_of_birth")
    private LocalDate dateOfBirth;       // date input      -> LocalDate (format conversion)

    private String gender;               // radio buttons   -> String (one value)

    private String department;           // drop-down list  -> String (one value)

    @ElementCollection(fetch = FetchType.EAGER)
    @CollectionTable(name = "student_courses", joinColumns = @JoinColumn(name = "student_id"))
    @Column(name = "course")
    private List<String> courses = new ArrayList<>();   // checkboxes -> List<String> (many values)

    @Column(length = 500)
    private String address;              // textarea        -> String

    public Student() {
    }

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }

    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 Integer getAge() { return age; }
    public void setAge(Integer age) { this.age = age; }

    public LocalDate getDateOfBirth() { return dateOfBirth; }
    public void setDateOfBirth(LocalDate dateOfBirth) { this.dateOfBirth = dateOfBirth; }

    public String getGender() { return gender; }
    public void setGender(String gender) { this.gender = gender; }

    public String getDepartment() { return department; }
    public void setDepartment(String department) { this.department = department; }

    public List<String> getCourses() { return courses; }
    public void setCourses(List<String> courses) { this.courses = courses; }

    public String getAddress() { return address; }
    public void setAddress(String address) { this.address = address; }
}

Step 6: Create below Repository Interface

package com.example.formbinding.repository;

import com.example.formbinding.entity.Student;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface StudentRepository extends JpaRepository<Student, Long> {
}

Step 7: Create below Service class

package com.example.formbinding.service;

import com.example.formbinding.entity.Student;
import com.example.formbinding.repository.StudentRepository;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class StudentService {

    private final StudentRepository studentRepository;

    public StudentService(StudentRepository studentRepository) {
        this.studentRepository = studentRepository;
    }

    public Student register(Student student) {
        return studentRepository.save(student);
    }

    public List<Student> getAll() {
        return studentRepository.findAll();
    }
}

Step 8: Create Controller class

package com.example.formbinding.controller;

import com.example.formbinding.entity.Student;
import com.example.formbinding.service.StudentService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import java.util.List;

@Controller
@RequestMapping("/students")
public class StudentController {

    private static final List<String> GENDERS = List.of("Male", "Female", "Other");
    private static final List<String> DEPARTMENTS = List.of("CSE", "ECE", "MECH", "CIVIL");
    private static final List<String> COURSES = List.of("Java", "Spring Boot", "SQL", "React", "Python");

    private final StudentService studentService;

    public StudentController(StudentService studentService) {
        this.studentService = studentService;
    }

    // Never let a form submit set the database id
    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.setDisallowedFields("id");
    }

    // GET /students/register -> shows the empty form (an empty Student is bound to it)
    @GetMapping("/register")
    public String showForm(Model model) {
        model.addAttribute("student", new Student());
        model.addAttribute("genders", GENDERS);
        model.addAttribute("departments", DEPARTMENTS);
        model.addAttribute("courseOptions", COURSES);
        return "register";
    }

    // POST /students/register -> Spring fills the Student object from the submitted fields
    @PostMapping("/register")
    public String register(@ModelAttribute("student") Student student, RedirectAttributes redirectAttributes) {
        Student saved = studentService.register(student);
        redirectAttributes.addFlashAttribute("message",
                "Student '" + saved.getName() + "' registered successfully with id " + saved.getId());
        return "redirect:/students";
    }

    // POST /students/quick-register -> binding single fields with @RequestParam
    @PostMapping("/quick-register")
    @ResponseBody
    public Student quickRegister(@RequestParam String name,
                                 @RequestParam String email,
                                 @RequestParam(defaultValue = "CSE") String department) {
        Student student = new Student();
        student.setName(name);
        student.setEmail(email);
        student.setDepartment(department);
        return studentService.register(student);
    }

    // GET /students -> HTML table of registered students
    @GetMapping
    public String list(Model model) {
        model.addAttribute("students", studentService.getAll());
        return "students";
    }

    // GET /students/json -> same data as JSON (to verify binding in Postman / browser)
    @GetMapping("/json")
    @ResponseBody
    public List<Student> listJson() {
        return studentService.getAll();
    }
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Student Registration</title>
    <style>
        body { font-family: Arial, sans-serif; background: #f4f6f8; margin: 0; padding: 30px; }
        .card { max-width: 560px; margin: auto; background: #fff; padding: 25px 30px; border-radius: 8px;
                box-shadow: 0 2px 8px rgba(0,0,0,.12); }
        h2 { margin-top: 0; }
        label.title { display: block; margin: 14px 0 5px; font-weight: bold; }
        input[type=text], input[type=email], input[type=number], input[type=date], select, textarea {
            width: 100%; padding: 8px; box-sizing: border-box; border: 1px solid #bbb; border-radius: 4px; }
        .inline label { margin-right: 15px; font-weight: normal; }
        button { margin-top: 20px; padding: 10px 22px; background: #2563eb; color: #fff; border: 0;
                 border-radius: 4px; cursor: pointer; font-size: 15px; }
        a { color: #2563eb; }
    </style>
</head>
<body>
<div class="card">
    <h2>Student Registration</h2>

    <!-- th:object binds the whole form to the 'student' model object -->
    <form th:action="@{/students/register}" th:object="${student}" method="post">

        <label class="title">Full Name</label>
        <input type="text" th:field="*{name}" placeholder="Enter name" required>

        <label class="title">Email</label>
        <input type="email" th:field="*{email}" placeholder="Enter email">

        <label class="title">Age</label>
        <input type="number" th:field="*{age}" min="1" max="100">

        <label class="title">Date of Birth</label>
        <input type="date" th:field="*{dateOfBirth}">

        <label class="title">Gender</label>
        <div class="inline">
            <label th:each="g : ${genders}">
                <input type="radio" th:field="*{gender}" th:value="${g}"> <span th:text="${g}"></span>
            </label>
        </div>

        <label class="title">Department</label>
        <select th:field="*{department}">
            <option value="">-- Select Department --</option>
            <option th:each="d : ${departments}" th:value="${d}" th:text="${d}"></option>
        </select>

        <label class="title">Courses</label>
        <div class="inline">
            <label th:each="c : ${courseOptions}">
                <input type="checkbox" th:field="*{courses}" th:value="${c}"> <span th:text="${c}"></span>
            </label>
        </div>

        <label class="title">Address</label>
        <textarea th:field="*{address}" rows="3" placeholder="Enter address"></textarea>

        <button type="submit">Register</button>
    </form>

    <p><a th:href="@{/students}">View registered students</a></p>
</div>
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Registered Students</title>
    <style>
        body { font-family: Arial, sans-serif; background: #f4f6f8; margin: 0; padding: 30px; }
        .card { max-width: 1000px; margin: auto; background: #fff; padding: 25px 30px; border-radius: 8px;
                box-shadow: 0 2px 8px rgba(0,0,0,.12); }
        table { width: 100%; border-collapse: collapse; margin-top: 15px; }
        th, td { border: 1px solid #ddd; padding: 8px 10px; text-align: left; font-size: 14px; }
        th { background: #2563eb; color: #fff; }
        .msg { background: #dcfce7; color: #166534; padding: 10px 14px; border-radius: 4px; }
        a { color: #2563eb; }
    </style>
</head>
<body>
<div class="card">
    <h2>Registered Students</h2>

    <div class="msg" th:if="${message}" th:text="${message}"></div>

    <table>
        <thead>
        <tr>
            <th>ID</th><th>Name</th><th>Email</th><th>Age</th><th>Date of Birth</th>
            <th>Gender</th><th>Department</th><th>Courses</th><th>Address</th>
        </tr>
        </thead>
        <tbody>
        <tr th:each="s : ${students}">
            <td th:text="${s.id}"></td>
            <td th:text="${s.name}"></td>
            <td th:text="${s.email}"></td>
            <td th:text="${s.age}"></td>
            <td th:text="${s.dateOfBirth}"></td>
            <td th:text="${s.gender}"></td>
            <td th:text="${s.department}"></td>
            <td th:text="${#strings.listJoin(s.courses, ', ')}"></td>
            <td th:text="${s.address}"></td>
        </tr>
        <tr th:if="${#lists.isEmpty(students)}">
            <td colspan="9">No students registered yet.</td>
        </tr>
        </tbody>
    </table>

    <p><a th:href="@{/students/register}">Register another student</a></p>
</div>
</body>
</html>

Step 9: Main Application

package com.example.formbinding;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class StudentFormBindingApplication {

    public static void main(String[] args) {
        SpringApplication.run(StudentFormBindingApplication.class, args);
    }
}

Step 10: Run the StudentFormBindingApplication

Scroll to Top