Spring Boot + Thymeleaf + Maven Example

By Arvind Rai, June 01, 2017
This page will walk through Spring Boot + Thymeleaf + Maven example. We will provide here how to use Spring Boot using 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 provide how to use Thymeleaf higher version using Maven if Spring Boot starter is using lower version of Thymeleaf. We will also use CSS files with our Thymeleaf view. Now let us discuss the complete example step by step.

Technologies Used

Find the technologies being used in our application.
1. Java 8
2. Spring Boot 1.5.3.RELEASE
3. Thymeleaf 3.0
4. Maven 3.3
5. Eclipse Mars

Integrate Thymeleaf 3 with Spring Boot

To integrate Thymeleaf with Spring Boot, we need to use following Spring Boot starter in our build tool such as Maven and Gradle. Suppose we are using Maven, then we will use following Maven dependency.
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency> 
This is all about to configure Thymeleaf with Spring Boot. When Spring Boot finds Thymeleaf library in classpath, then Spring Boot automatically configures required settings for Thymeleaf.
At the time of writing this article, the latest version of Thymeleaf starter is 1.5.3.RELEASE and it uses Thymeleaf 2.1 version. If we want to use higher version of Thymeleaf for example Thymeleaf 3.0 then we need to configure following properties in pom.xml.
1. thymeleaf.version
2. thymeleaf-layout-dialect.version

Suppose we are using Maven then the above properties will be configured as follows.
<properties>
   <thymeleaf.version>3.0.6.RELEASE</thymeleaf.version>
   <thymeleaf-layout-dialect.version>2.2.2</thymeleaf-layout-dialect.version>
   <java.version>1.8</java.version>        
</properties> 
Now Spring Boot will use Thymeleaf version 3.0.6.RELEASE and Thymeleaf layout dialect version 2.2.2 and java version 1.8 .

Using Thymeleaf

If Spring Boot scans Thymeleaf library in its classpath then 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.

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> 

Validation

To validate a field we can use Hibernate validator. When we use Spring Boot starter, the Hibernate validator library is configured automatically in classpath. Now we will use validator annotations in our class that will bind the form. The validator annotations are such as @NotNull, @Size etc from javax.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 javax.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.

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."); 



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.

1. Project Structure using Eclipse

Find the demo project structure using eclipse.
Spring Boot + Thymeleaf + Maven Example

2. Maven File

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.concretepage</groupId>
	<artifactId>spring-boot-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>
	<name>spring-demo</name>
	<description>Spring Boot Demo Project</description>
	<parent>
	   <groupId>org.springframework.boot</groupId>
	   <artifactId>spring-boot-starter-parent</artifactId>
	   <version>1.5.3.RELEASE</version>
	</parent>
	<properties>
	   <thymeleaf.version>3.0.6.RELEASE</thymeleaf.version>
           <thymeleaf-layout-dialect.version>2.2.2</thymeleaf-layout-dialect.version>
	   <java.version>1.8</java.version>        
	</properties>
	<dependencies>
	    <dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	    </dependency>
	    <dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-thymeleaf</artifactId>
	    </dependency>	  
    	    <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <optional>true</optional>
            </dependency> 
	</dependencies> 
	<build>
	    <plugins>
		<plugin>
		   <groupId>org.springframework.boot</groupId>
		   <artifactId>spring-boot-maven-plugin</artifactId>
		</plugin>
	     </plugins>
	</build>
</project> 
Find the Spring Boot starter description used in our example.
spring-boot-starter-parent: Parent POM for dependency management.
spring-boot-starter-web: Starter for building web, REST applications. It uses tomcat server as default embedded server.
spring-boot-starter-thymeleaf : Used in Thymeleaf integration with Spring Framework.
spring-boot-devtools : It provides developer tools. These tools are helpful in application development mode. One of the features of developer tool is automatic restart of the server for any change in code.

3. Create Domain and Service

User.java
package com.concretepage.domain;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
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;
   public String getUserId() {
	return userId;
   }
   public void setUserId(String userId) {
	this.userId = userId;
   }
   public String getUserName() {
	return userName;
   }
   public void setUserName(String userName) {
	this.userName = userName;
   }
   public String getGender() {
	return gender;
   }
   public void setGender(String gender) {
	this.gender = gender;
   }
   public Boolean getMarried() {
	return married;
   }
   public void setMarried(Boolean married) {
	this.married = married;
   }
   public String getProfile() {
	return profile;
   }
   public void setProfile(String profile) {
	this.profile = profile;
   }  
}  
We are using Hibernate validator for form validation in the above class. Now find the service class.
UserService.java
package com.concretepage.service;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;
import com.concretepage.domain.User;
@Service
public class UserService {
	private List<User> allUsers = new ArrayList<>();
	public List<User> getAllUserArticles(){
		return allUsers;
	}
	public void addUser(User user) {
		allUsers.add(user);
	}
} 

4. Create Controller

UserController.java
package com.concretepage.controller;
import java.util.ArrayList;
import java.util.List;
import javax.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import com.concretepage.domain.User;
import com.concretepage.service.UserService;
@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;
	}
} 

5. Create application.properties

application.properties
logging.level.root= WARN
logging.level.org.springframework.web= ERROR
logging.level.com.concretepage= INFO 
We have configured our log level in above property file.

6. 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. 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> 

8. Create CSS File

styles.css
.error{
    color: red;
    font-size: 15px;
}
.user{
    color: blue;
    font-size: 15px;
}
table {
    border-collapse: collapse;
}

table, th, td {
    border: 1px solid black;
} 

9. Spring Boot Main Class

Find Spring boot Main class to run the application.
MyApplication.java
package com.concretepage;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyApplication {  
    public static void main(String[] args) {
	SpringApplication.run(MyApplication.class, args);
    }       
} 

Run Application

We can run our application in following ways.
1. Using Eclipse: Download the project source code using the download link given at the end of page. Import the project into eclipse. Using command prompt, go to the root folder of the project and run.
mvn clean eclipse:eclipse 
and then refresh the project in eclipse. Run Main class MyApplication by clicking Run as -> Java Application. Tomcat server will be started.

2. Using Maven Command: Download the project source code. Go to the root folder of the project using command prompt and run the command.
mvn spring-boot:run 
Tomcat server will be started.

3. Using Executable JAR: Using command prompt, go to the root folder of the project and run the command.
mvn clean package 
We will get executable JAR spring-boot-demo-0.0.1-SNAPSHOT.jar in target folder. Run this JAR as
java -jar target/spring-boot-demo-0.0.1-SNAPSHOT.jar 
Tomcat server will be started.

Now we are ready to 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 + Maven Example
After successful form submit, we will get report page of users.
Spring Boot + Thymeleaf + Maven Example


I am done now. Happy Spring Boot learning!

References

Spring Boot Reference Guide
Using Thymeleaf

Download Source Code

POSTED BY
ARVIND RAI
ARVIND RAI









©2023 concretepage.com | Privacy Policy | Contact Us