Spring Boot Thymeleaf Example

By Arvind Rai, 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.

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 in application.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 
Now we will discuss how to create form elements using Thymeleaf.

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;
  }
  ---
} 
When we access /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()); 
The attribute name 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) {
    ---
  }
} 
Now we will create some form elements. Suppose we have a class that has fields corresponding to form elements.
public class User { 
   private String userId;
   private String userName;
   private String gender;
   private Boolean married;
   private String profile;
} 
Data binding with form elements such as <input> and <select> is achieved by Thymeleaf 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}" /> 
In the same way, other form element will be bound with User class fields. Find the input box for userName field of User class.
<input type="text" th:field="*{userName}" /> 
Find the radio button for gender field of User class.
<input type="radio" th:field="*{gender}" value="Male"/>
<input type="radio" th:field="*{gender}" value="Female"/> 
Find the checkbox for married field of User class.
<input type="checkbox" th:field="*{married}" /> 
Find the select element for profile field of User class.
<select th:field="*{profile}">
         <option th:each="profile : ${allProfiles}" th:value="${profile}" th:text="${profile}">Profile</option>
</select> 
Thymeleaf 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;
} 
Thymeleaf provides 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> 
In Thymeleaf we can use href using 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 
If the locale is 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 
Then it will be displayed by Thymeleaf in following way.
<title th:text="#{app.title}"> Title </title> 
Thymeleaf provides #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> 
The above Spring Boot starter will resolve 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;
} 
To enable form validation, we need to use @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;
} 
If form has validation errors then 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 resolves spring-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 
Now in any class that is under 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);	
2. Now use logger anywhere in the class as required.
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.
Spring Boot Thymeleaf Example

7.2 Create Domain and Service

User.java
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;
   //Setters and Getters
}  
We are using Hibernate validator for form validation in the above class. Now find the service class.
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 for en 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> 
On successful form submit, following page will open.
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 
We will get following view to create a user.
Spring Boot Thymeleaf Example
After successful form submit, we will get report page of users.
Spring Boot Thymeleaf Example

8. References

Spring Boot Reference Guide
Using Thymeleaf

9. Download Source Code

POSTED BY
ARVIND RAI
ARVIND RAI







©2024 concretepage.com | Privacy Policy | Contact Us