LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 2319 min read

Handling Form Data

Learn how @ModelAttribute binds submitted HTML form fields directly onto a Java object in Spring MVC.

Introduction

Reading individual form fields one at a time with @RequestParam works fine for a single value, but gets unwieldy fast once a form has ten or fifteen fields. Spring MVC solves this with automatic form binding: you define a plain Java object whose fields match the form field names, and Spring populates an instance of it for you. In this lesson, you will build a form-backing object and wire it up with @ModelAttribute.

What You Will Learn
  • Why binding a whole object beats reading fields one by one.
  • How to create a form-backing object (a simple POJO).
  • How @ModelAttribute binds request parameters onto that object.
  • How to render a form and handle its submission end to end.

The Problem Form Binding Solves

Imagine a "create user" form with fields for name, email, age, and country. Reading each one individually means a handler method parameter list that grows every time the form changes.

// Painful without binding
@PostMapping("/users/create")
public String createUser(
@RequestParam String name,
@RequestParam String email,
@RequestParam int age,
@RequestParam String country) {
// build a User object manually from four parameters
return "redirect:/users";
}

A form-backing object lets Spring do this assembly automatically, and it also gives you one clean object to pass along to your service layer.

Creating a Form-Backing Object

A form-backing object is just a plain Java class with a no-argument constructor and a setter for every field you want bound. Its field names should match the name attributes used in the HTML form.

public class UserForm {
private String name;
private String email;
private int age;
private String country;
// getters and setters
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 int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getCountry() { return country; }
public void setCountry(String country) { this.country = country; }
}

Binding with @ModelAttribute

Add @ModelAttribute in front of a handler method parameter, and Spring will create an instance of that type, populate it from matching request parameters, and also automatically add it to the Model under a name derived from the class (userForm for UserForm).

@Controller
@RequestMapping("/users")
public class UserController {
@GetMapping("/create")
public String showForm(Model model) {
model.addAttribute("userForm", new UserForm());
return "user-form";
}
@PostMapping("/create")
public String submitForm(@ModelAttribute UserForm userForm) {
// userForm.getName(), .getEmail(), .getAge(), .getCountry() are all populated
userService.register(userForm);
return "redirect:/users";
}
}
Submitted Form Data

Click Run to see what this code prints.

Rendering the Form

On the GET side, the empty (or pre-filled) UserForm is added to the Model so the template can bind input fields to it. Using a template engine's form-binding tags (such as Thymeleaf's th:object and th:field) keeps field names in sync with the Java object automatically.

<form th:action="@{/users/create}" th:object="${userForm}" method="post">
<input type="text" th:field="*{name}" placeholder="Name" />
<input type="email" th:field="*{email}" placeholder="Email" />
<input type="number" th:field="*{age}" placeholder="Age" />
<input type="text" th:field="*{country}" placeholder="Country" />
<button type="submit">Create User</button>
</form>

Handling the Submission

When the form is submitted, the browser sends a POST request with the same field names as URL-encoded form data. The @ModelAttribute-annotated parameter on the submitForm method receives a fully populated UserForm - no manual parsing required.

@ModelAttribute Is Often Optional

For a POST handler with a single non-primitive parameter, Spring will treat it as a form-backing object even without an explicit @ModelAttribute annotation. Writing it explicitly is still recommended for clarity.

Common Mistakes

Avoid These Mistakes
  • Missing getters/setters on the form-backing object, which silently breaks binding for that field.
  • Mismatched field names between the HTML form and the Java object, leaving fields null or zero.
  • Forgetting to add the form-backing object to the Model on the GET handler, causing a null object error when the view renders.
  • Binding sensitive fields (like a role or isAdmin flag) directly from a public form without restricting which fields can be set.
  • Reusing the same form object for both create and update flows without accounting for an id field that only applies to updates.

Best Practices

  • Use a dedicated form-backing class per form instead of binding directly onto your persistence/entity class.
  • Keep form-backing objects simple - just fields, getters, and setters, no business logic.
  • Use a template engine's built-in form-binding support to keep field names synchronized automatically.
  • Validate bound data before acting on it (covered in the next lesson).
  • Map the bound form object to a proper domain object in the service layer, rather than passing it straight to persistence.

Frequently Asked Questions

Yes - it can bind query parameters on a GET request the same way it binds form fields on a POST, which is handy for search or filter forms.

Spring records a binding error instead of throwing an exception immediately. You inspect those errors with BindingResult, which is exactly what the next lesson on validation covers.

Yes, as long as the HTML field names use the dotted syntax address.city, address.zipCode, matching the nested object's properties.

Key Takeaways

  • A form-backing object is a plain Java class whose fields match a form's field names.
  • @ModelAttribute binds request parameters onto that object automatically.
  • The bound object is also added to the Model automatically under a name derived from its class.
  • Template engines like Thymeleaf offer form-binding tags that keep field names consistent with the Java object.
  • Keep form-backing objects separate from persistence entities.

Summary

Form binding turns raw submitted data into a ready-to-use Java object with almost no boilerplate. The next question is what happens when that data is wrong - missing a required field, or an age that is negative. That is exactly what Spring MVC validation handles.

Next Lesson →

Spring MVC Validation