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.
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 undertemplates/) - SPA vs server-side templates
1. Spring MVC and the front controller
MVC splits UI apps into:
| Piece | Role |
|---|---|
| Model | Data for the view |
| View | Renders HTML (here: Thymeleaf) |
| Controller | Handles 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:
- Request hits
DispatcherServlet HandlerMappingfinds a controller/handlerHandlerAdapterinvokes it- Controller runs business logic, builds a model, returns a view name
ViewResolvermaps name → real view (e.g.index→templates/index.html)- View renders model data to the response
Other collaborators:
LocaleResolver— client locale (default oftenAcceptHeaderLocaleResolver)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
}
}
MVC flow quiz
1 / 2In 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:
- SPA frontend (React/Angular/Vue) + Boot REST JSON API
- 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 formth: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>
Thymeleaf + Boot quiz
1 / 3Default location for Thymeleaf HTML in a Boot app is…
Pick an answer to see if you are right.
Chapter checklist
| Topic | Idea |
|---|---|
| Front controller | DispatcherServlet + mappings / adapters / views |
| Model | model.addAttribute → ${…} in templates |
| Thymeleaf | th:text, th:each, th:if / switch, forms |
| Validation | @Valid + BindingResult + #fields / th:errors |
| Verbs | Hidden method filter for PUT/DELETE |
| Layout | Templates under resources/templates |
Full samples: appendix02 on GitHub.