Skip to main content

Spring MVC and Thymeleaf

Source: Spring Boot in Practice — Appendix B by Somnath Musib (Manning). Concepts rewritten for this course; companion samples: spring-boot-in-practice/repo appendix02.

Server-rendered Course Tracker UI: DispatcherServlet, models, views, and Thymeleaf tags for text, loops, conditionals, and forms.

Pattern-check challenges

Editors do not compile or run Spring Boot / Thymeleaf. Check uses sourceChecks only. Snippets above exercises show the usual shape with inline comments; type the code yourself. Paste is disabled on Spring Boot lessons.

What this lesson covers​

  • MVC + front controller (DispatcherServlet) request flow
  • Key collaborators: HandlerMapping, HandlerAdapter, ViewResolver, locale & exception resolvers
  • Thymeleaf basics: th:text, th:each, conditionals, forms
  • Boot + Thymeleaf CRUD pattern (@Controller, templates under templates/)
  • SPA vs server-side templates

1. Spring MVC and the front controller​

MVC splits UI apps into:

PieceRole
ModelData for the view
ViewRenders HTML (here: Thymeleaf)
ControllerHandles HTTP, calls services, fills the model, returns a logical view name

Spring MVC sits on the IoC container. All requests go through a front controller: the DispatcherServlet. It does not do all work itself — it delegates.

Simplified flow:

  1. Request hits DispatcherServlet
  2. HandlerMapping finds a controller/handler
  3. HandlerAdapter invokes it
  4. Controller runs business logic, builds a model, returns a view name
  5. ViewResolver maps name → real view (e.g. index → templates/index.html)
  6. View renders model data to the response

Other collaborators:

  • LocaleResolver — client locale (default often AcceptHeaderLocaleResolver)
  • HandlerExceptionResolver — map errors to error views / status (or pass along)

In Boot web apps, DispatcherServlet is auto-configured; you rarely write web.xml by hand.

@Controller
public class CourseController {

@GetMapping("/index")
public String index(Model model) {
model.addAttribute("courses", courseService.findAllCourses()); // key → view
return "index"; // logical view → templates/index.html
}
}
Quiz

MVC flow quiz

1 / 2

In Spring MVC, the DispatcherServlet is…

Pick an answer to see if you are right.


2. Thymeleaf overview​

Thymeleaf is a server-side template engine (HTML, XML, TEXT, JS, CSS, RAW). HTML templates mix normal HTML with th:* attributes. The engine replaces them with data from the Spring model, then sends plain HTML to the browser.

Declare the namespace:

<html xmlns:th="http://www.thymeleaf.org">

Boot: add spring-boot-starter-thymeleaf and put templates in src/main/resources/templates/.

Two common web styles with Boot:

  1. SPA frontend (React/Angular/Vue) + Boot REST JSON API
  2. MVC + template engine (Thymeleaf) — same app serves HTML views

This lesson focuses on (2).


3. Displaying model attributes​

Controller:

@GetMapping
public String getLoggedInUserName(Model model) {
model.addAttribute("userName", user.getName());
return "index";
}

Template:

Logged in user: <span th:text="${userName}"></span> <!-- ${key} from model -->

Iterate a list:

<tr th:each="course : ${courses}">
<td th:text="${course.id}"></td>
<td th:text="${course.name}"></td>
<td th:text="${course.description}"></td>
</tr>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<span><!-- show userName --></span>
<table>
<tbody>
<!-- each course row -->
</tbody>
</table>
</body>
</html>

4. Conditionals​

<span th:if="${author.gender} == 'M'" th:text="Male"></span>
<span th:unless="${author.gender} == 'M'" th:text="Female"></span>

Switch / case:

<div th:switch="${course.category}">
<div th:case="'Spring'"><h2>Spring Course</h2></div>
<div th:case="'Python'"><h2>Python Course</h2></div>
<div th:case="*"><h2>Some other course</h2></div> <!-- default -->
</div>
<td>
<!-- Male if gender == 'M', else Female -->
</td>

5. Forms, validation, and hidden HTTP methods​

Form binding:

<form th:action="@{/addcourse}" th:object="${course}" method="post">
<input type="text" th:field="*{name}" />
<span th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></span>
<button type="submit">Add Course</button>
</form>
  • th:action="@{...}" — form POST URL (Thymeleaf link expression)
  • th:object — command object bound to the form
  • th:field="*{prop}" — property on that object
  • #fields.hasErrors / th:errors — show Bean Validation messages

Controller pattern:

@PostMapping("/addcourse")
public String addCourse(@Valid Course course, BindingResult result, Model model) {
if (result.hasErrors()) {
return "add-course"; // redisplay form
}
courseService.createCourse(course);
return "redirect:/index";
}

Browsers mainly send GET/POST. For PUT/DELETE, enable:

spring.mvc.hiddenmethod.filter.enabled=true

Then in the template:

<form th:action="@{/delete/{id}(id=${course.id})}" th:method="delete">
<button type="submit">Delete</button>
</form>

Thymeleaf emits a hidden _method field; HiddenHttpMethodFilter rewrites the verb.

<form action="#" method="post">
<!-- th:action, th:object, name field + errors -->
</form>
@Controller
public class CourseController {

@PostMapping("/addcourse")
public String addCourse(@Valid Course course, BindingResult result, Model model) {
// if errors → "add-course"; else create + redirect:/index
return null;
}
}
# enable PUT/DELETE from HTML forms via _method

6. End-to-end Course Tracker (MVC + Thymeleaf)​

Typical pieces:

  • @Controller (not @RestController) — returns view names
  • Service interface + in-memory or JPA impl
  • Entity with Bean Validation (@NotEmpty, @Min / @Max)
  • Templates: index.html, add-course.html, update-course.html
  • CRUD mappings: GET list/forms, POST create, PUT update, DELETE remove

Index page often uses #lists.size(courses) with th:switch for empty vs filled table, th:href for edit links, and th:method="delete" for deletes.

@GetMapping("/index")
public String index(Model model) {
model.addAttribute("courses", courseService.findAllCourses());
return "index";
}
<a th:href="@{/update/{id}(id=${course.id})}">Edit</a>
Quiz

Thymeleaf + Boot quiz

1 / 3

Default location for Thymeleaf HTML in a Boot app is…

Pick an answer to see if you are right.


Chapter checklist​

TopicIdea
Front controllerDispatcherServlet + mappings / adapters / views
Modelmodel.addAttribute → ${…} in templates
Thymeleafth:text, th:each, th:if / switch, forms
Validation@Valid + BindingResult + #fields / th:errors
VerbsHidden method filter for PUT/DELETE
LayoutTemplates under resources/templates

Full samples: appendix02 on GitHub.