Spring Boot Thymeleaf Example
December 04, 2023
This page will walk through Spring Boot Thymeleaf example. We will learn here how to use Spring Boot and Thymeleaf with internationalization (i18n), form validation and logging. If Spring Boot scans Thymeleaf library in classpath, it will automatically configures Thymeleaf. We can change the default Thymeleaf configurations using application.properties
. Thymeleaf is a server-side template engine that can process XML, HTML etc. Thymeleaf can access a class fields, message properties from i18n messages files. We can bind our class fields with HTML form elements using Thymeleaf. We can iterate our java collections using Thymeleaf. In our example we will perform form validation and dispplay i18n messages using Thymeleaf. We will also use CSS files with our Thymeleaf view. Now let us discuss the complete example step-by-step.
Contents
1. Technologies Used
Find the technologies being used in our application.1. Java 20
2. Spring Boot 3.1.3
3. Thymeleaf 3.1.2
2. Thymeleaf Maven Dependency
To integrate Thymeleaf with Spring Boot, we need to resolve following Spring Boot starter in our Maven dependency.<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
If we want to use other version of Thymeleaf for example Thymeleaf 3.0 then we need to configure following properties in
pom.xml
.
<properties> <thymeleaf.version>3.0.6.RELEASE</thymeleaf.version> <thymeleaf-layout-dialect.version>2.2.2</thymeleaf-layout-dialect.version> </properties>
3. Using Thymeleaf
Spring Boot scans Thymeleaf library in its classpath and we are ready to work with Thymeleaf. Spring Boot provides properties for Thymeleaf that will be configured inapplication.properties
or application.yml
to change the configurations of Thymeleaf with Spring Boot. We are listing some of them here.
spring.thymeleaf.mode: Template mode that will be applied on templates. Default is
HTML 5
.
spring.thymeleaf.prefix: This is the value that will be prepended with view name to build the URL. Default value is
classpath:/templates/
.
spring.thymeleaf.suffix: This is the value that will be appended with view name to build the URL. Default value is
.html
.
With the default Spring Boot and Thymeleaf configuration we can keep our Thymeleaf files with html extension at following location.
src\main\resources\templates
Creating <form> using Thymeleaf
Suppose we have a method in controller class.
@Controller @RequestMapping("app") public class UserController { @GetMapping("create-user") public ModelAndView createUserView() { ModelAndView mav = new ModelAndView(); mav.setViewName("user-creation"); mav.addObject("user", new User()); mav.addObject("allProfiles", getProfiles()); return mav; } --- }
/app/create-user
URL, the Thymeleaf file named as user-creation.html
will open and there we will create a form to create user. Now look into the code snippet given below.
mav.addObject("user", new User());
user
will be used in <form> with Thymeleaf th:object
attribute.
<form action="#" th:action="@{/app/create-user}" th:object="${user}" method="POST">
th:action
is assigned with action URL that will be called on form submit. On form submit, the URL /app/create-user
will access following methods in controller class.
@Controller @RequestMapping("app") public class UserController { --- @PostMapping("create-user") public ModelAndView createUser(@Valid User user, BindingResult result) { --- } }
public class User { private String userId; private String userName; private String gender; private Boolean married; private String profile; }
th:field
attribute. Suppose we want to create an input text box to get user id. The field userId
of User
class will be bound to input text box using Thymeleaf th:field
as given below.
<input type="text" th:field="*{userId}" />
User
class fields. Find the input box for userName
field of User
class.
<input type="text" th:field="*{userName}" />
gender
field of User
class.
<input type="radio" th:field="*{gender}" value="Male"/> <input type="radio" th:field="*{gender}" value="Female"/>
married
field of User
class.
<input type="checkbox" th:field="*{married}" />
profile
field of User
class.
<select th:field="*{profile}"> <option th:each="profile : ${allProfiles}" th:value="${profile}" th:text="${profile}">Profile</option> </select>
th:each
will iterate the collection. Thymeleaf th:value
attribute assigns value. allProfiles
is the attribute name that will be added in ModelAndView
for every request.
mav.addObject("allProfiles", getProfiles());
getProfiles()
method will return the list of all profiles.
private List<String> getProfiles() { List<String> list = new ArrayList<>(); list.add("Developer"); list.add("Manager"); list.add("Director"); return list; }
th:text="${...}"
syntax that can be used to access a value from a java object. If we want to display a report of all items in a table, we write code as follows.
<table> <tr th:each="user : ${allUsers}"> <td th:text="${user.userId}">Id</td> <td th:text="${user.userName}">Title</td> </tr> </table>
th:href="@{..}"
syntax for loading CSS or to create a link to redirect a URL.
4. Internationalization (i18n)
When we use Spring Boot starter, our Spring Boot application is ready to use internationalization (i18n). We need to create properties file in following location.src\main\resources\messages.properties src\main\resources\messages_en.properties
en
then Spring application will pick messages_en.properties
file. The default i18n file will be messages.properties
.
We can change default spring message configurations using spring properties in
application.properties
file. For example,
spring.messages.basename: It configures comma separated base names for message files. Default is
messages
.
spring.messages.encoding: It configures message bundle encoding. Default is UTF-8
To display i18n messages, we need to use Thymeleaf
th:text="#{...}"
syntax. Suppose we want to display i18n message for our page title and for that we have a key defined in our message properties file as given below.
app.title= Spring Boot + Thymeleaf
<title th:text="#{app.title}"> Title </title>
#locale
context using which we can display locale specific details such as language, country as follows.
<p th:text="${#locale.language}"> language</p> <p th:text="${#locale.country}"> country</p>
5. Validation
To validate a field, Spring Boot uses Hibernate validator. When we use Spring Boot validator starter, the Hibernate validator library is configured automatically in classpath. Use following Maven dependency for validation.<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
jakarta.validation
and Hibernate validator dependencies.
Now we will use validator annotations in our class that will bind the form. The validator annotations are as
@NotNull
, @Size
from jakarta.validation
library.
public class User { @NotNull @Size(min=3, max=10) private String userId; @NotNull @Size(min=5, max=20) private String userName; @NotNull private String gender; private Boolean married; private String profile; }
@Valid
annotation from jakarta.validation
library in our Spring controller method argument that will be called on form submit. We also need the instance of Spring BindingResult
to know whether form is validated or not.
@PostMapping("create-user") public ModelAndView createUser(@Valid User user, BindingResult result) { ModelAndView mav = new ModelAndView(); if(result.hasErrors()) { logger.info("Validation errors while submitting form."); mav.setViewName("user-creation"); mav.addObject("user", user); mav.addObject("allProfiles", getProfiles()); return mav; } userService.addUser(user); mav.addObject("allUsers", userService.getAllUserArticles()); mav.setViewName("user-info"); logger.info("Form submitted successfully."); return mav; }
result.hasErrors()
will return true. If validation errors are there in the form then we should call the same Thymeleaf view again and if no validation errors then perform business logic and redirect to success view.
Now in Thymeleaf view we need to display validation error messages. To check if there is validation errors for a given field name, Thymeleaf provides
#fields.hasErrors(...)
that returns boolean values.
<label th:if="${#fields.hasErrors('userId')}" th:errors="*{userId}" th:class="'error'">Id Error</label>
th:errors
will display default validation error message of Spring on the basis of Hibernate validator annotation that we have used in our class fields bound with the form elements. If we want to use i18n message for validation errors, then use th:text="#{...}
.
<label th:if="${#fields.hasErrors('userId')}" th:text="#{error.user.id}" th:class="'error'">Id Error</label>
error.user.id
is the i18n properties defined in messages_en.properties
file.
6. Logging
In Spring Boot application Logback dependency is resolved automatically. Every Spring Boot starter resolvesspring-boot-starter-logging
itself. To change the default configuration of logging, we need to configure logging properties in application.properties
file. Find some of the those properties.
logging.level.*: It is used as prefix with package name to set log level.
logging.file: It configures a log file name to log message in file.
logging.path: It only configures path for log file. Spring boot creates a log file with name
spring.log
.
Suppose our class package is
com.concretepage
then log level can be defined in application.properties
file using logging.level.*
as follows.
logging.level.com.concretepage= INFO
com.concretepage
package or its sub package, the log level INFO
will be applied. We can use org.slf4j.Logger
as follows.
1. Instantiate
org.slf4j.Logger
at class level.
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
logger.info("Validation errors while submitting form.");
7. Complete Example
Find the complete example of Spring boot with Thymeleaf. We will create our application with internalization (i18n), form validation and logging. Find the complete code.7.1 Project Structure using Eclipse
Find the demo project structure using eclipse.
7.2 Create Domain and Service
User.javapublic class User { @NotNull @Size(min=3, max=10) private String userId; @NotNull @Size(min=5, max=20) private String userName; @NotNull private String gender; private Boolean married; private String profile; //Setters and Getters }
UserService.java
@Service public class UserService { private List<User> allUsers = new ArrayList<>(); public List<User> getAllUserArticles(){ return allUsers; } public void addUser(User user) { allUsers.add(user); } }
7.3 Create Controller
UserController.java@Controller @RequestMapping("app") public class UserController { private static final Logger logger = LoggerFactory.getLogger(UserController.class); @Autowired private UserService userService; @GetMapping("create-user") public ModelAndView createUserView() { ModelAndView mav = new ModelAndView(); mav.setViewName("user-creation"); mav.addObject("user", new User()); mav.addObject("allProfiles", getProfiles()); return mav; } @PostMapping("create-user") public ModelAndView createUser(@Valid User user, BindingResult result) { ModelAndView mav = new ModelAndView(); if(result.hasErrors()) { logger.info("Validation errors while submitting form."); mav.setViewName("user-creation"); mav.addObject("user", user); mav.addObject("allProfiles", getProfiles()); return mav; } userService.addUser(user); mav.addObject("allUsers", userService.getAllUserArticles()); mav.setViewName("user-info"); logger.info("Form submitted successfully."); return mav; } private List<String> getProfiles() { List<String> list = new ArrayList<>(); list.add("Developer"); list.add("Manager"); list.add("Director"); return list; } }
7.4 Create Message Properties for Internationalization
Find the message properties foren
locale.
messages_en.properties
app.title= Spring Boot + Thymeleaf app.create-user= Create User app.user-details= User Details user.id= Id user.name= Name user.gender= Gender user.married= Married user.profile= Profile user.gender.male= Male user.gender.female= Female user.form.submit= Submit user.form.reset= Reset user.add-more-users= Add More Users error.user.id= Id must be between 3 to 10 characters. error.user.name= User name must be between 5 to 20 characters. error.user.gender= Select gender.
7.5 Create Thymeleaf Templates
When we access the application, following page will open.user-creation.html
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <head> <title th:text="#{app.title}"> Title </title> <link rel="stylesheet" th:href="@{/css/styles.css}"/> </head> <body> <h1 th:text="#{app.create-user}">Create</h1> <form action="#" th:action="@{/app/create-user}" th:object="${user}" method="POST"> <table> <tr><td th:text="#{user.id}"/></td> <td> <input type="text" th:field="*{userId}" /> <label th:if="${#fields.hasErrors('userId')}" th:text="#{error.user.id}" th:class="'error'">Id Error</label> </td> </tr> <tr><td th:text="#{user.name}"></td> <td> <input type="text" th:field="*{userName}" /> <label th:if="${#fields.hasErrors('userName')}" th:text="#{error.user.name}" th:class="'error'">Name Error</label> </td> </tr> <tr><td th:text="#{user.gender}"></td> <td> <input type="radio" th:field="*{gender}" value="Male"/><label th:text="#{user.gender.male}">M</label> <input type="radio" th:field="*{gender}" value="Female"/><label th:text="#{user.gender.female}">F</label> <label th:if="${#fields.hasErrors('gender')}" th:text="#{error.user.gender}" th:class="'error'">Gender Error</label> </td> </tr> <tr><td th:text="#{user.married}+ '?'"></td> <td> <input type="checkbox" th:field="*{married}" /> </td> </tr> <tr><td th:text="#{user.profile}"></td> <td> <select th:field="*{profile}"> <option th:each="profile : ${allProfiles}" th:value="${profile}" th:text="${profile}">Profile</option> </select> </td> </tr> <tr><td colspan="2"> <input type="submit" th:value="#{user.form.submit}"/> <input type="reset" th:value="#{user.form.reset}"/> </td> </tr> </table> </form> </body> </html>
user-info.html
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <head> <title th:text="#{app.title}"> Title </title> <link rel="stylesheet" th:href="@{/css/styles.css}"/> </head> <body> <h1 th:text="#{app.user-details}">Details</h1> <table> <tr> <th th:text="#{user.id}"/></th> <th th:text="#{user.name}"></th> <th th:text="#{user.gender}"></th> <th th:text="#{user.married}+ '?'"></th> <th th:text="#{user.profile}"></th> </tr> <tr th:each="user : ${allUsers}"> <td th:text="${user.userId}">Id</td> <td th:text="${user.userName}">Title</td> <td th:text="${user.gender}">Gender</td> <td th:text="${user.married}">Married</td> <td th:text="${user.profile}">Profile</td> </tr> </table> <a href="#" th:href="@{/app/create-user}" th:text="#{user.add-more-users}">Add User</a> </body> </html>
7.6 Main Class
Find Spring boot Main class to run the application.MyApplication.java
@SpringBootApplication public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } }
7.7 Output
Download the source code and test the application. Access the URL as given below.http://localhost:8080/app/create-user


8. References
Spring Boot Reference GuideUsing Thymeleaf