Spring BootModel
Spring Boot

Model

Model is the Spring MVC interface for passing data from a controller to a view template. When a @Controller method renders a Thymeleaf or JSP template, it populates a Model with named attributes — objects, lists, strings, flags — that the template can access by name. Model, ModelMap, and ModelAndView are the three ways to work with model data in Spring MVC.

Model, ModelMap, and @ModelAttribute

Spring MVC provides three ways to add attributes to the model. Model is the recommended interface — it is injected by Spring and provides a clean API for adding named attributes. ModelMap is the concrete implementation of Model with additional Map operations. @ModelAttribute on a method parameter binds a form object from the request.
Java
@Controller
@RequestMapping("/users")
public class UserPageController {

    private final UserService userService;

    // ── Model — injected directly as a method parameter: ──────────────
    @GetMapping
    public String listUsers(Model model) {
        model.addAttribute("users", userService.findAll());
        model.addAttribute("pageTitle", "All Users");
        model.addAttribute("totalCount", userService.count());
        return "users/list";   // view name → templates/users/list.html
    }

    // ── ModelMap — concrete implementation, same result: ──────────────
    @GetMapping("/{id}")
    public String userDetail(@PathVariable Long id, ModelMap modelMap) {
        modelMap.addAttribute("user", userService.findById(id));
        modelMap.addAttribute("orders", orderService.findByUser(id));
        return "users/detail";
    }

    // ── Map<String, Object> — simplest form, works identically: ───────
    @GetMapping("/dashboard")
    public String dashboard(Map<String, Object> model) {
        model.put("stats", userService.getStats());
        model.put("recentUsers", userService.findRecent(10));
        return "users/dashboard";
    }

    // ── @ModelAttribute on a method — adds attribute to ALL handlers: ─
    // Runs before every handler method in this controller:
    @ModelAttribute("appName")
    public String appName() {
        return "MyApplication";   // available as ${appName} in every template
    }

    @ModelAttribute("currentUser")
    public UserResponse currentUser(Principal principal) {
        return userService.findByUsername(principal.getName());
    }
}

Passing Data to Templates

Model attributes become variables in the template context. Thymeleaf accesses them with ${attributeName} expressions. The controller is responsible for naming attributes clearly and consistently.
Java
// ── Controller: ───────────────────────────────────────────────────────
@GetMapping
public String listUsers(
        Model model,
        @RequestParam(defaultValue = "0") int page,
        @RequestParam(defaultValue = "") String search) {

    Page<UserResponse> users = userService.search(search, PageRequest.of(page, 20));

    model.addAttribute("users", users.getContent());
    model.addAttribute("currentPage", page);
    model.addAttribute("totalPages", users.getTotalPages());
    model.addAttribute("totalElements", users.getTotalElements());
    model.addAttribute("search", search);
    model.addAttribute("hasNext", users.hasNext());
    model.addAttribute("hasPrevious", users.hasPrevious());

    return "users/list";
}

<!-- Template — accessing model attributes: -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>

    <input type="text" th:value="${search}" name="search" />

    <p th:text="|Showing ${users.size()} of ${totalElements} users|"></p>

    <tr th:each="user : ${users}">
        <td th:text="${user.name}"></td>
        <td th:text="${user.email}"></td>
    </tr>

    <a th:if="${hasPrevious}"
       th:href="@{/users(page=${currentPage - 1}, search=${search})}">Previous</a>
    <a th:if="${hasNext}"
       th:href="@{/users(page=${currentPage + 1}, search=${search})}">Next</a>

</body>
</html>

@ModelAttribute on Method Parameters

@ModelAttribute on a method parameter binds the request (form fields, query parameters) to a Java object. This is the primary mechanism for receiving HTML form submissions in Spring MVC.
Java
// ── Form command object — fields match form input names: ─────────────
public class CreateUserForm {
    @NotBlank private String name;
    @NotBlank @Email private String email;
    private User.Role role = User.Role.USER;
    // getters + setters (required for binding)
}

@Controller
@RequestMapping("/users")
public class UserFormController {

    // ── GET — display the empty form: ─────────────────────────────────
    @GetMapping("/new")
    public String newUserForm(Model model) {
        model.addAttribute("userForm", new CreateUserForm());  // empty form object
        model.addAttribute("roles", User.Role.values());
        return "users/form";
    }

    // ── POST — receive the submitted form: ────────────────────────────
    @PostMapping
    public String createUser(
            @ModelAttribute("userForm") @Valid CreateUserForm form,
            BindingResult bindingResult,
            Model model) {

        if (bindingResult.hasErrors()) {
            // Re-render the form — form object retains the invalid values:
            model.addAttribute("roles", User.Role.values());
            return "users/form";   // back to the form template
        }

        userService.create(form);
        return "redirect:/users";  // PRG pattern — redirect after success
    }
}

// ── @ModelAttribute binding sources (in order): ───────────────────────
// 1. @ModelAttribute method in the same controller (pre-populated object)
// 2. HTTP session (@SessionAttributes)
// 3. Request parameters and path variables (form fields, query params)
// If no source found: a new instance is created via the default constructor.

RedirectAttributes — Passing Data Across Redirects

A redirect (return "redirect:/...") causes the browser to make a new GET request, which clears the model. RedirectAttributes passes data across the redirect boundary — flash attributes survive exactly one redirect and are removed automatically.
Java
@Controller
@RequestMapping("/users")
public class UserFormController {

    // ── POST → redirect → GET (Post/Redirect/Get pattern): ────────────
    @PostMapping
    public String create(
            @ModelAttribute @Valid CreateUserForm form,
            BindingResult bindingResult,
            RedirectAttributes redirectAttributes) {

        if (bindingResult.hasErrors()) {
            return "users/form";
        }

        UserResponse created = userService.create(form);

        // Flash attribute — survives one redirect, then removed:
        redirectAttributes.addFlashAttribute("successMessage",
            "User '" + created.name() + "' created successfully");

        // Regular redirect attribute — appended to the URL as query param:
        redirectAttributes.addAttribute("highlight", created.id());

        return "redirect:/users";
        // Redirects to: /users?highlight=43
        // Flash: successMessage available in /users model, then gone
    }
}

// ── Receiving in the redirect target: ─────────────────────────────────
@GetMapping
public String listUsers(Model model,
        @RequestParam(required = false) Long highlight) {
    model.addAttribute("users", userService.findAll());
    model.addAttribute("highlight", highlight);
    // successMessage is already in the model automatically from flash storage
    return "users/list";
}

<!-- Template — display the flash message: -->
<div th:if="${successMessage}" class="alert alert-success">
    <p th:text="${successMessage}"></p>
</div>
<tr th:each="user : ${users}"
    th:classappend="${user.id == highlight} ? 'highlighted' : ''">
    ...
</tr>