Thymeleaf Template Engine Guide
Introduction
Thymeleaf is a modern server-side Java template engine for web and standalone environments. It’s commonly used with Spring Framework.
Setup
Add the Thymeleaf namespace to your HTML:
<html xmlns:th="http://thymeleaf.org">
Variable Expression
Display Variable Value
Use th:text to display variable values:
<h4 th:text="${user.name}"></h4>
Conditional Display
th:if Attribute
Display content conditionally:
<p th:if="${param.error}">
Bad Credentials: ${param.error}
</p>
Common Thymeleaf Attributes
| Attribute | Description |
|---|---|
th:text |
Replace text content |
th:if |
Conditional rendering |
th:unless |
Negative conditional rendering |
th:each |
Loop iteration |
th:href |
Dynamic link |
th:src |
Dynamic source |
th:value |
Form field value |
th:object |
Form backing object |
th:field |
Form field binding |
Reference
Spring Boot Integration
Thymeleaf integrates seamlessly with Spring Boot. Simply add the dependency and templates are automatically resolved.
Maven Dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
Gradle Dependency
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
Controller Example
@Controller
public class UserController {
@GetMapping("/users")
public String listUsers(Model model) {
List<User> users = userService.findAll();
model.addAttribute("users", users);
model.addAttribute("title", "User List");
return "users/list"; // resolves to templates/users/list.html
}
}
Templates are placed in src/main/resources/templates/ by default.
Iteration with th:each
Loop through collections to generate repeated HTML elements:
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr th:each="user, iterStat : ${users}">
<td th:text="${user.name}">John</td>
<td th:text="${user.email}">john@example.com</td>
<td th:text="${user.active ? 'Active' : 'Inactive'}">Active</td>
</tr>
</tbody>
</table>
The iteration status variable (iterStat) provides useful properties:
| Property | Description |
|---|---|
index |
Zero-based index |
count |
One-based index |
size |
Total number of elements |
even / odd |
Boolean for even/odd iteration |
first / last |
Boolean for first/last element |
URL Expressions
Thymeleaf provides a special syntax for building URLs with @{}:
<!-- Static resource -->
<link th:href="@{/css/style.css}" rel="stylesheet">
<!-- Path variable -->
<a th:href="@{/users/{id}(id=${user.id})}">View</a>
<!-- Query parameter -->
<a th:href="@{/users(page=${currentPage}, size=10)}">Next</a>
<!-- Combines path variable and query parameter -->
<a th:href="@{/users/{id}/edit(id=${user.id}, tab='profile')}">Edit</a>
Form Handling
Thymeleaf provides powerful form binding capabilities with Spring MVC:
<form th:action="@{/users}" th:object="${userForm}" method="post">
<div>
<label>Name:</label>
<input type="text" th:field="*{name}"/>
<span th:if="${#fields.hasErrors('name')}"
th:errors="*{name}" class="error"></span>
</div>
<div>
<label>Email:</label>
<input type="email" th:field="*{email}"/>
<span th:if="${#fields.hasErrors('email')}"
th:errors="*{email}" class="error"></span>
</div>
<div>
<label>Role:</label>
<select th:field="*{role}">
<option th:each="role : ${roles}"
th:value="${role}"
th:text="${role.displayName}">Role</option>
</select>
</div>
<button type="submit">Save</button>
</form>
The th:object attribute binds the form to a model object, and th:field handles both the name attribute and the value binding automatically.
Fragment Reuse (Layouts)
Fragments allow you to define reusable template components:
Defining Fragments
<!-- templates/fragments/layout.html -->
<header th:fragment="header">
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
</header>
<footer th:fragment="footer(year)">
<p>© <span th:text="${year}">2025</span> My Company</p>
</footer>
Using Fragments
<!-- templates/pages/home.html -->
<div th:insert="~{fragments/layout :: header}"></div>
<main>
<h1>Welcome</h1>
</main>
<div th:replace="~{fragments/layout :: footer(${#dates.year(#dates.createNow())})}"></div>
The difference between th:insert and th:replace:
th:insertinserts the fragment inside the host tagth:replacereplaces the host tag entirely with the fragment
Utility Objects
Thymeleaf provides built-in utility objects accessible with the # prefix:
| Object | Purpose | Example |
|---|---|---|
#strings |
String operations | ${#strings.isEmpty(name)} |
#numbers |
Number formatting | ${#numbers.formatDecimal(price, 1, 2)} |
#dates |
Date formatting | ${#dates.format(date, 'yyyy-MM-dd')} |
#lists |
List operations | ${#lists.size(items)} |
#objects |
Object null check | ${#objects.nullSafe(obj, 'default')} |
Natural Templates Advantage
One of Thymeleaf’s greatest strengths is that templates are valid HTML files. This means designers can open and view them in a browser without a running server. The th:text attributes are ignored by browsers, and the placeholder content is displayed instead:
<!-- This is valid HTML that displays "John Doe" in a browser -->
<!-- When processed by Thymeleaf, it shows the actual user name -->
<span th:text="${user.name}">John Doe</span>
This makes Thymeleaf particularly well-suited for teams where designers and developers collaborate on the same template files.
Comments