Thymeleaf is a modern Java template engine that gives you a convenient and flexible way to create dynamic HTML pages. It integrates with Spring MVC and supports dual-mode rendering: HTML files can be used as static templates (for example, for frontend testing) and as server-side dynamically generated pages.
Thymeleaf — a smart helper in web development
You know why devs love Thymeleaf? It makes HTML come alive and dynamic. Imagine: you write plain HTML and just add special attributes — that's the whole trick. Even if your server is down, the browser will still render your HTML without breaking.
Also, Thymeleaf works great for any project. Whether you're building a tiny page or a big portal, it fits. And yeah, it plays nicely with Spring out of the box!
Want to get how it works? It's kinda like building a game. You have an HTML template — that's the game's skeleton. Data are the game assets. Thymeleaf takes the skeleton and brings it to life, like a game engine. It places data where it belongs, adds touches, and turns plain markup into a full dynamic page.
Adding Thymeleaf to a Spring MVC project
Maven dependencies. First, you need to add Thymeleaf to your project. Put the following dependency into pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
If you're using Gradle, add this line to build.gradle:
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
Project structure. After adding Thymeleaf, you'll have a folder for templates. By default it's:
src/main/resources/templates
All your HTML templates should live in that folder. Spring Boot will automatically "see" them and load them.
Thymeleaf syntax basics
Extending HTML with attributes. Thymeleaf uses its own attributes that start with th:. For example:
- th:text — for rendering text.
- th:href — for links.
- th:each — for loops.
- th:if/th:unless — for conditions.
A simple example
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Hello, Thymeleaf!</title>
</head>
<body>
<h1>Welcome, <span th:text="${username}"></span>!</h1>
</body>
</html>
Here th:text="${username}" will replace the content of the span with the value of the username variable passed from the controller.
Passing data from the controller
Let's create a simple controller that sends data to a template.
@Controller
public class WelcomeController {
@GetMapping("/")
public String welcome(Model model) {
model.addAttribute("username", "Junior Programmer");
return "welcome"; // points to the welcome.html template
}
}
Note:
- We use the
Modelobject to pass data to the template. - The returned value
return "welcome";points to thewelcome.htmlfile inside thetemplatesfolder.
Example HTML template (welcome.html)
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Welcome</title>
</head>
<body>
<h1>Hello, <span th:text="${username}"></span>!</h1>
</body>
</html>
If you open this in a browser, you'll see:
Hello, Junior Programmer!
Dynamic tables with Thymeleaf
Let's do something a bit more advanced. For example, render a list of students in a table.
Controller
@Controller
public class StudentController {
@GetMapping("/students")
public String listStudents(Model model) {
List<String> students = List.of("Anna", "Ivan", "Maria", "Sergey");
model.addAttribute("students", students);
return "students"; // template students.html
}
}
We use the Java 9+ method List.of(...) to keep it simple.
Template (students.html)
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Students List</title>
</head>
<body>
<h1>Students List</h1>
<table border="1">
<thead>
<tr>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr th:each="student : ${students}">
<td th:text="${student}"></td>
</tr>
</tbody>
</table>
</body>
</html>
Here:
th:each="student : ${students}"— loops over thestudentscollection.- Inside the loop we render the student's name with
th:text="${student}".
Result in the browser:
Students List
-------------------
Name
Anna
Ivan
Maria
Sergey
Conditions in Thymeleaf
Sometimes you need to show or hide elements based on conditions.
Example using th:if and th:unless
<p th:if="${isAdmin}">Welcome, administrator!</p>
<p th:unless="${isAdmin}">Welcome, user!</p>
In the controller:
model.addAttribute("isAdmin", true);
Result. If isAdmin = true, you'll see:
Welcome, administrator!
Creating hyperlinks
Thymeleaf makes it easy to build links using path expressions.
Example
<a th:href="@{/students}">Students List</a>
Output:
<a href="/students">Students List</a>
Using @{} also works with dynamic parameters:
<a th:href="@{/student/{id}(id=${studentId})}">View student</a>
Integrating forms with Thymeleaf
Thymeleaf supports working with HTML forms. This is especially handy for collecting user input.
Controller example
@Controller
public class FormController {
@GetMapping("/form")
public String showForm(Model model) {
model.addAttribute("student", new Student());
return "form";
}
@PostMapping("/form")
public String processForm(@ModelAttribute Student student) {
System.out.println("Received student: " + student);
return "form-success";
}
}
Form template (form.html)
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Form</title>
</head>
<body>
<form action="#" th:action="@{/form}" th:object="${student}" method="post">
Name: <input type="text" th:field="*{name}"><br>
Age: <input type="number" th:field="*{age}"><br>
<button type="submit">Submit</button>
</form>
</body>
</html>
th:action and
th:field for automatic binding to the model via the
student object.
Handy tips and common mistakes
Most mistakes when using Thymeleaf come from:
- Missing dependencies. Check that
spring-boot-starter-thymeleafis included. - Wrong folder structure. Make sure your HTML templates are in
resources/templates. - Thymeleaf syntax errors. Verify your
th:*attributes are spelled correctly.
For deeper learning, check out the official Thymeleaf documentation.
GO TO FULL VERSION