Thymeleaf
Thymeleaf is a modern, open-source server-side Java template engine designed for processing and rendering templates in both web and standalone environments, enabling the creation of dynamic content from natural HTML, XML, JavaScript, CSS, or plain text files that double as static prototypes.[1][2] Originally created by Spanish software engineer Daniel Fernández, it was first released in stable form as version 1.0.0 on July 17, 2011, and has since evolved through multiple versions, with the latest being 3.1.3.RELEASE issued on December 9, 2024.[3][4][5]
A core strength of Thymeleaf lies in its natural templating approach, which allows developers to embed server-side logic using HTML attributes (e.g., th:text for dynamic text substitution) without breaking the validity of the markup, facilitating seamless collaboration between designers and developers as templates can be previewed directly in browsers.[2] It supports various template modes including HTML5, XML, TEXT, JAVASCRIPT, CSS, and RAW, and is highly extensible through custom dialects and processors for tailored behaviors.[2] Thymeleaf is particularly renowned for its tight integration with the Spring Framework, via modules like Thymeleaf Spring, which make it a default choice for server-side rendering in Spring Boot applications, though it also works standalone or with other ecosystems such as Java EE 8 MVC, Play Framework, and IDE plugins for Eclipse and IntelliJ IDEA.[1][6]
Beyond web development, Thymeleaf's versatility extends to non-web use cases like email templating or report generation, emphasizing maintainable code through features such as expression inlining, decoupled template logic, and support for internationalization.[2] Maintained by a collaborative team under the Apache License 2.0, it prioritizes web standards compliance and performance, making it a robust tool for building dynamic user interfaces in Java-based projects.[3][7]
Overview
History
Thymeleaf was created in 2011 by Daniel Fernández, a Spanish software engineer, as an open-source Java template engine licensed under the Apache License 2.0.[3][1]
The project's initial stable release, version 1.0, arrived on July 17, 2011, emphasizing natural templating that allows HTML, XHTML, and XML prototypes to be opened and viewed in browsers without processing, while supporting server-side rendering.[4]
Thymeleaf 2.0 followed in February 2012, delivering major performance enhancements through a new custom DOM representation, along with features like the th:switch attribute for conditional processing and improved Spring Framework integration via dedicated dialects for seamless MVC view resolution.[8][6]
Version 3.0 was released on May 8, 2016, introducing support for additional textual template modes such as TEXT, JAVASCRIPT, and CSS, as well as compatibility with Java 8 features including the time API through integrated extras modules.[9][10]
Thymeleaf 3.1 debuted on November 17, 2022, with refinements to security protocols and expanded compatibility including Servlet 5.0 (Jakarta EE 9+) and Spring Framework 6.0.[11][12]
As of November 2025, the stable release remains in the 3.1.x series, with version 3.1.3 issued on December 9, 2024; the project continues under active maintenance by the Thymeleaf team.[5]
Design Principles
Thymeleaf's design centers on the concept of natural templating, which ensures that templates are valid HTML or XML documents that can be opened directly in a web browser as static prototypes without any server-side processing. This approach facilitates seamless collaboration between designers and developers by allowing static files to serve as both prototypes and fully functional templates once processed, reducing the need for separate mockup files. By embedding dynamic instructions primarily through standard HTML attributes rather than custom tags or scripts, Thymeleaf maintains document validity and readability.[2]
A core architectural choice is server-side rendering tailored for Java environments, emphasizing the avoidance of inline programming code within templates to prevent mixing presentation with application logic. This promotes adherence to Model-View-Controller (MVC) patterns, where template logic remains decoupled from business logic, enabling cleaner separation of concerns and easier maintenance. Thymeleaf achieves this through attribute-based processing, where server-side evaluation replaces placeholders with dynamic content, such as via its expression language for inserting values without embedding Java snippets.[2]
The engine supports multiple markup languages through pluggable template modes, including HTML, XML, TEXT, JAVASCRIPT, CSS, and RAW, allowing flexible processing of diverse file types in both web and standalone contexts. This modularity enables developers to handle various output formats without custom extensions, while the offline prototyping capability ensures designers can iterate on templates independently of a running server, viewing them as static assets in any browser.[2]
Core Concepts
Template Processing
Thymeleaf's template processing follows a structured pipeline that transforms static template files into dynamic output by integrating data from a context. The process begins with the parsing phase, where the template engine uses markup language-specific parsers—such as the AttoParser for HTML mode—to convert the template into a sequence of events, including tags, attributes, and text content, without performing validation in HTML mode to accommodate real-world HTML irregularities.[2] This event-based representation allows for efficient manipulation while preserving the template's structure.
Following parsing, the selection phase applies CSS-like Markup Selectors to identify specific elements or fragments within the template for targeted processing, enabling precise application of transformations to relevant nodes.[2] The subsequent processing phase evaluates template attributes and expressions in a predefined order of precedence, where attribute processors—such as those for iteration or conditional rendering—modify the event sequence by incorporating data from the context, ultimately generating the rendered output.[2] This pipeline supports natural templating, allowing templates to produce valid static HTML when no dynamic processing occurs.[2]
At the core of this pipeline is the TemplateEngine, with the StandardTemplateEngine serving as the primary implementation for executing templates. It handles template resolution by delegating to one or more ITemplateResolver instances, such as ServletContextTemplateResolver for web applications, which locate templates based on names, patterns, and resource checks.[2] The engine also manages caching of parsed templates to optimize performance, configurable via parameters like cacheable patterns and time-to-live (TTL) durations, with a default cache size of up to 200 entries managed by a StandardCacheManager.[2]
Data is passed to templates through Context objects, which implement the IContext interface (or IWebContext for web environments) and maintain a variables map for holding model attributes from controllers, along with locale information.[2] Variable scopes are organized hierarchically, allowing access to request-specific variables at the context root, session variables via a session map, and application-wide variables through the servlet context, facilitating scoped data isolation in web applications.[2]
Error handling in template processing emphasizes robustness, particularly in dynamic environments. Template resolution failures occur gracefully if a resolver cannot locate a resource, returning null without throwing exceptions, though developers can enable existence checks to preempt issues.[2] Expression evaluation exceptions arise from invalid syntax or unresolvable variables, which the engine propagates unless mitigated by safe navigation operators or default values, ensuring predictable behavior during rendering.[2]
For non-web scenarios, Thymeleaf supports batch processing modes using a standalone Context object to supply data, making it suitable for generating reports, emails, or other offline outputs without servlet dependencies.[2] This configuration leverages textual template modes like TEXT or JAVASCRIPT for flexibility in batch environments.[2]
Expression Language
Thymeleaf employs the Standard Expression Language (TEL), a domain-specific language designed for evaluating dynamic expressions within templates, primarily based on the OGNL (Object-Graph Navigation Language) expression parser but extended with specialized expression types for templating needs.[2] TEL enables developers to access variables, perform conditionals, and construct URLs without embedding scripting code directly into HTML, promoting natural templating.[2] Unlike general-purpose expression languages, TEL emphasizes simplicity and safety, avoiding complex scripting features like loops, which are instead managed through dedicated template attributes such as th:each.[2]
Variable expressions, denoted by ${...}, allow access to variables in the template context, supporting property projections (e.g., ${user.name} to retrieve the name property of a user object) and collection selections (e.g., ${users[0]} to access the first element in a users list).[2] These expressions facilitate straightforward data retrieval and manipulation, such as concatenating strings or invoking methods on objects, while adhering to OGNL's navigation syntax for bean properties and safe navigation to prevent null pointer exceptions.[2]
Selection expressions, using the syntax *{...}, operate within the context of a selected object typically established by the th:object attribute, enabling concise property access relative to that object (e.g., *{firstName} refers to the firstName of the selected form bean).[2] This is particularly useful in form handling, where it simplifies binding and display of object properties without repeating the full variable path.[2]
Message expressions, formatted as #{...}, retrieve internationalized messages from resource bundles, supporting key-based lookups (e.g., #{home.welcome} resolves to a localized string like "Welcome" from a properties file).[2] Parameters can be interpolated within these expressions for dynamic content, such as #{message(${user.name})}, enhancing support for multilingual applications.[2]
Link expressions, specified with @{...}, build URLs by handling context paths, parameters, and relative/absolute paths automatically (e.g., @{/product/list(id=${id})} generates a full URL like /myapp/product/list?id=123 in a web context).[2] This abstraction ensures portability across deployment environments, including support for query string parameters and fragment links within templates.[2]
Conditional expressions in TEL include ternary if/then/else operations (e.g., ${user.active} ? 'Active' : 'Inactive'), switch/case logic (though primarily via template attributes like th:switch and th:case), and boolean evaluations using operators such as ==, !=, and, or, and not.[2] Booleans are represented as literals true and false, allowing direct comparisons (e.g., ${condition} == true).[2]
Literal tokens form the basic building blocks of expressions, encompassing text literals enclosed in single quotes (e.g., 'hello'), numbers as integers (e.g., 42) or decimals (e.g., 3.14), booleans (true or false), and the null value (null).[2] String literals support escaping with a backslash (e.g., 'It\'s here' for embedding single quotes), ensuring robust handling of special characters without breaking expression parsing.[2]
In Spring Framework integrations, TEL can leverage Spring's SpEL (Spring Expression Language) as an alternative parser for more advanced expression capabilities while retaining the same syntax.[2]
Key Features
Natural Templating
Thymeleaf's natural templating enables the creation of HTML templates that serve as fully valid static documents, allowing them to be opened directly in web browsers or design editors without any processing, which promotes seamless collaboration between designers and developers.[13] This approach contrasts with traditional server-side technologies like JSP, where embedded scriptlets disrupt HTML structure and cause validation failures.[13] By leveraging custom HTML5 attributes prefixed with "th:", Thymeleaf injects dynamic behavior while ensuring the unprocessed template remains compliant with HTML standards and ignored by browsers.[13]
Central to this feature are attributes like th:text and th:utext, which handle text content replacement. The th:text attribute substitutes the element's inner text with a model value, automatically escaping HTML to mitigate cross-site scripting risks; for example:
html
<p th:text="${user.name}">John Doe</p>
<p th:text="${user.name}">John Doe</p>
displays "John Doe" statically but renders the actual user name when processed.[13] In contrast, th:utext inserts unescaped HTML, enabling the rendering of formatted content such as links or markup from the model.[13] These attributes rely on Thymeleaf's expression language to evaluate and populate values from the data context.[13]
Template fragments enhance reusability through th:fragment, which defines modular components that can be included across templates. For instance:
html
<div th:fragment="sidebar">
<ul>
<li th:each="item : ${menuItems}" th:text="${item}"></li>
</ul>
</div>
<div th:fragment="sidebar">
<ul>
<li th:each="item : ${menuItems}" th:text="${item}"></li>
</ul>
</div>
This fragment can be incorporated using th:insert to embed it within a host element or th:replace to substitute the host entirely, as in <div th:replace="~{sidebar :: sidebar}"></div>.[12] The th:insert preserves the host tag while adding the fragment's content, whereas th:replace discards it for cleaner composition.[13]
Dynamic styling is achieved via selector-based attributes like th:class and th:style, which process CSS without altering the static HTML. The th:class attribute appends or sets classes conditionally, such as <div th:class="${isActive} ? 'active' : ''">Content</div>, while th:style applies inline styles from expressions, like <p th:style="'color: ' + ${color}">Text</p>.[13] This attribute-driven model ensures templates validate against HTML5 standards in their raw form, unlike JSP's intrusive scripting that often results in invalid markup.[13]
Internationalization Support
Thymeleaf provides robust support for internationalization (i18n) through its natural templating approach, allowing developers to externalize text into locale-specific resource bundles while keeping templates readable as static HTML. Messages are resolved using the #{key} syntax in expressions, which retrieves values from properties files such as messages.properties for the default locale or messages_es.properties for Spanish. These resource bundles are typically placed in the classpath or template directory, and Thymeleaf's StandardMessageResolver automatically loads the appropriate file based on the current locale.[14]
Locale detection in Thymeleaf occurs automatically in web environments via the HttpServletRequest object, where the Accept-Language header from the client's request determines the preferred locale, or through explicit setting in the template context using IWebExchange.setLocale(). In standalone mode, the locale defaults to the system's default or can be manually specified when creating the processing context. This ensures that templates dynamically adapt content without hardcoding language-specific strings. For example, a template might include <p th:text="#{home.welcome}">Welcome</p>, which resolves to "Bienvenido" for Spanish locales.[14]
Parameter substitution enhances dynamic i18n by allowing placeholders in message strings, formatted using Java's MessageFormat. Messages in resource bundles can define placeholders like {0} or named parameters, enabling expressions such as #{title(${session.user.name})} to insert values at runtime, as in <p th:text="#{greeting('John', 'friend')}">Hello</p>. This supports flexible, context-aware translations without compromising template maintainability.[14]
Pluralization is handled through integration with a MessageSource implementation, which applies locale-specific rules using MessageFormat's choice format for basic pluralization (e.g., singular and plural forms). For languages with more complex plural rules, such as Arabic or Russian, integration with libraries like ICU4J is recommended. Developers configure a single key with choice syntax, such as items={0,choice,0#No items|1#One item|1<{0} items} in resource bundles, resolved via #{items(${itemCount})} in templates.[14]
When integrated with the Spring Framework, Thymeleaf leverages Spring's MessageSource—typically ResourceBundleMessageSource—for enhanced resolution, including caching of loaded bundles to improve performance in multi-threaded environments. The SpringTemplateEngine automatically wires the MessageSource bean, allowing #{ } expressions to benefit from Spring's hierarchical resolution and fallback mechanisms across locales. Configuration involves setting the basename in the MessageSource bean, such as setBasename("messages"), ensuring seamless i18n in Spring MVC applications.[13]
Integration and Usage
Spring Framework Integration
Thymeleaf integrates seamlessly with the Spring Framework, particularly within Spring MVC and Spring Boot applications, enabling the use of Thymeleaf templates as views without extensive manual configuration. In Spring Boot, auto-configuration is provided through the spring-boot-starter-thymeleaf dependency, which automatically sets up the necessary components when Thymeleaf is detected on the classpath. This includes creating a SpringTemplateEngine and a ThymeleafViewResolver that resolves view names returned by controllers to .html template files located in the src/main/resources/templates directory by default.[15]
The ThymeleafViewResolver maps logical view names from Spring controllers directly to Thymeleaf templates, supporting features like internationalization and fragment inclusion without requiring additional resolvers for static content. For form handling, Thymeleaf provides native support for binding Spring model attributes using attributes such as th:object to specify the form-backing object and th:field to bind individual fields, leveraging Spring's expression language for validation and conversion. This integration ensures that form submissions are processed efficiently within Spring MVC's request handling pipeline.[13]
When using Spring Security, Thymeleaf automatically incorporates CSRF protection by processing form actions with th:action, which injects the required CSRF token as a hidden field via Spring's RequestDataValueProcessor. This requires the thymeleaf-extras-springsecurity module and basic Spring Security configuration, allowing secure form submissions without manual token management. For performance, Thymeleaf's caching is enabled by default in Spring Boot via the SpringResourceTemplateResolver, with cache behavior configurable through properties like spring.thymeleaf.cache; while Thymeleaf uses its own cache manager, it can be customized to align with Spring's caching abstractions for template efficiency in larger applications. Thymeleaf 3.1 provides dedicated support for Spring Framework 6.0 via the thymeleaf-spring6 library, including compatibility with Servlet API 5.0 and the jakarta.* namespace.[16][15][12]
Unlike JSP, which often requires web.xml modifications and tag library descriptors, Thymeleaf integration with Spring relies purely on annotation-based configuration, such as @Controller methods returning view names, making it more lightweight and suitable for modern microservices. This approach eliminates the need for JSP-specific resolvers or compilation steps, allowing templates to function as static HTML during development.[13]
Standalone Configuration
Thymeleaf can be configured and used in standalone Java applications without any web framework by including the core Thymeleaf dependency in the project build file. For Maven-based projects, this involves adding the org.thymeleaf:thymeleaf artifact, with the latest version being 3.1.3.RELEASE as of December 2024, which requires Java SE 8 or newer.[5] Gradle users can similarly declare implementation 'org.thymeleaf:thymeleaf:3.1.3.RELEASE'. This minimal setup enables template processing in non-web contexts, such as console applications or batch jobs.[14]
To build a TemplateEngine instance, first create a resolver to locate templates, then attach it to the engine. A common choice is the ClassLoaderTemplateResolver for loading templates from the classpath. For example:
java
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
import org.thymeleaf.templateresolver.ITemplateResourceConfiguration;
ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();
resolver.setPrefix("templates/"); // Optional: directory prefix
resolver.setSuffix(".html"); // Optional: file extension
resolver.setTemplateMode("HTML"); // Or "TEXT" for plain text
resolver.setCharacterEncoding("UTF-8");
TemplateEngine engine = new TemplateEngine();
engine.setTemplateResolver(resolver);
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
import org.thymeleaf.templateresolver.ITemplateResourceConfiguration;
ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();
resolver.setPrefix("templates/"); // Optional: directory prefix
resolver.setSuffix(".html"); // Optional: file extension
resolver.setTemplateMode("HTML"); // Or "TEXT" for plain text
resolver.setCharacterEncoding("UTF-8");
TemplateEngine engine = new TemplateEngine();
engine.setTemplateResolver(resolver);
This configuration allows the engine to resolve and parse templates efficiently, with options for caching and encoding to optimize performance in standalone scenarios.[14]
Templates are processed programmatically by preparing a context with variables and invoking the engine's process method. The Context class holds the model data, and output is directed to a Writer. A basic example for generating HTML content is:
java
import org.thymeleaf.context.Context;
import java.io.StringWriter;
Context context = new Context();
context.setVariable("name", "World");
StringWriter writer = new StringWriter();
engine.process("hello", context, writer); // "hello" is the template name
String result = writer.toString();
import org.thymeleaf.context.Context;
import java.io.StringWriter;
Context context = new Context();
context.setVariable("name", "World");
StringWriter writer = new StringWriter();
engine.process("hello", context, writer); // "hello" is the template name
String result = writer.toString();
Thymeleaf's expression language enables data binding within templates during this process, allowing dynamic insertion of variables like ${name}.[14]
Standalone Thymeleaf is particularly suited for use cases like email templating, where the TEXT template mode generates plain text bodies with embedded logic such as loops. For instance, a template might iterate over a product list to create an order summary email. It also supports PDF generation through integration with libraries like iText, where the engine produces an HTML string that iText converts to PDF. Batch report generation benefits from this setup, enabling the creation of customized text or HTML reports in offline processes.[14][17]
Resource resolution in standalone mode is flexible, supporting multiple loaders. The ClassLoaderTemplateResolver handles classpath resources, ideal for bundled templates in JAR files. For file system access, use FileTemplateResolver with a base directory path, such as new FileTemplateResolver(new File("/path/to/templates")). URL-based resolution via UrlTemplateResolver allows fetching templates from remote locations, like new UrlTemplateResolver().setTemplateResourceResolver(new UrlTemplateResourceResolver("https://example.com/templates/")). Each resolver can be configured independently to match the application's resource strategy.[14]
Examples and Best Practices
Basic Template Example
A basic example of Thymeleaf usage in a minimal Spring Boot application demonstrates how to display dynamic user data in an HTML template. This setup assumes the inclusion of the spring-boot-starter-thymeleaf dependency in the project's Maven or Gradle build file, along with a simple @Controller class and templates placed in the src/main/resources/templates directory.[18][19]
The controller populates the model with a user object and a list of items before returning the template name. For instance, consider a User class with name and active properties:
java
public class User {
private String name;
private boolean active;
// constructors, getters, setters
}
public class User {
private String name;
private boolean active;
// constructors, getters, setters
}
A @GetMapping handler might look like this:
java
@Controller
public class BasicController {
@GetMapping("/basic")
public String basicExample(Model model) {
User user = new User("John Doe", true);
model.addAttribute("user", user);
List<String> hobbies = Arrays.asList("Reading", "Hiking", "Coding");
model.addAttribute("hobbies", hobbies);
return "basic-template";
}
}
@Controller
public class BasicController {
@GetMapping("/basic")
public String basicExample(Model model) {
User user = new User("John Doe", true);
model.addAttribute("user", user);
List<String> hobbies = Arrays.asList("Reading", "Hiking", "Coding");
model.addAttribute("hobbies", hobbies);
return "basic-template";
}
}
This code adds the user object and hobbies list to the model, enabling access via Spring Expression Language (SpEL) in the template.[20][6]
The corresponding template, basic-template.html, uses Thymeleaf attributes to insert dynamic content while maintaining valid static HTML. Key attributes include th:text for text substitution, th:each for iteration, and th:if for conditionals:
html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Basic [Thymeleaf](/page/Thymeleaf) Example</title>
</head>
<body>
<h1>Welcome, <span th:text="${user.name}">Guest</span>!</h1>
<div th:if="${user.active}">
<p>Account is active.</p>
</div>
<div th:unless="${user.active}">
<p>Account is inactive.</p>
</div>
<h2>Hobbies:</h2>
<ul>
<li th:each="hobby : ${hobbies}" th:text="${hobby}">Placeholder</li>
</ul>
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Basic [Thymeleaf](/page/Thymeleaf) Example</title>
</head>
<body>
<h1>Welcome, <span th:text="${user.name}">Guest</span>!</h1>
<div th:if="${user.active}">
<p>Account is active.</p>
</div>
<div th:unless="${user.active}">
<p>Account is inactive.</p>
</div>
<h2>Hobbies:</h2>
<ul>
<li th:each="hobby : ${hobbies}" th:text="${hobby}">Placeholder</li>
</ul>
</body>
</html>
Here, th:text="${user.name}" replaces the span's content with the user's name, th:if and th:unless conditionally render paragraphs based on the active flag, and th:each iterates over the hobbies list to populate unordered list items.[2][20]
When viewed statically in a browser (without server processing), the template displays placeholder text like "Welcome, Guest!" and a list with "Placeholder" repeated, allowing for design prototyping. Upon processing by Thymeleaf in the Spring Boot application, the output becomes dynamic: for the example data, it renders "Welcome, John Doe!", "Account is active.", and a list of "Reading", "Hiking", "Coding". Thymeleaf's natural templating ensures the unprocessed HTML remains browser-viewable.[2][19]
Thymeleaf facilitates form processing by integrating seamlessly with Spring MVC's data binding and validation mechanisms, allowing templates to bind form inputs to Java objects and display validation errors directly in the HTML. This approach uses the th:object attribute to establish a form backing object and th:field for individual field bindings, enabling automatic population of form values from the model and submission back to the controller.[21]
In a typical template, the form is defined with th:object="${user}" to bind to a model attribute, such as a User object, and fields use the selection expression *{}, like <input type="text" th:field="*{username}" />, which resolves to the property path on the bound object. This syntax supports nested properties and leverages Spring's Conversion Service for type conversion. For submission, the form specifies th:action="@{/processUser}" with method="post", and when using Spring Security, a CSRF token is automatically included as a hidden field via the RequestDataValueProcessor interface.[21]
Validation is handled through integration with Jakarta Bean Validation, where annotations like @NotBlank or @Email on the backing object's fields trigger checks during binding. Errors are displayed using th:errors="*{username}" for field-specific messages, often wrapped in a conditional like <p th:if="${#fields.hasErrors('username')}" th:errors="*{username}">Invalid input.</p>. Styling can be applied conditionally with th:errorclass="error" on input elements to highlight invalid fields. Error messages can be internationalized by referencing message keys from resource bundles.[21]
On the controller side, a @PostMapping("/processUser") method receives the bound object and a BindingResult parameter to inspect validation outcomes: if errors exist (bindingResult.hasErrors()), the model is repopulated with the object and the form template is redisplayed; otherwise, processing proceeds, such as saving to a database. For example:
java
@PostMapping("/processUser")
public String processUser(@ModelAttribute User user, BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
return "userForm"; // Redisplay form with errors
}
// Process successful submission
return "success";
}
@PostMapping("/processUser")
public String processUser(@ModelAttribute User user, BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
return "userForm"; // Redisplay form with errors
}
// Process successful submission
return "success";
}
This ensures user-friendly error handling without custom parsing.[21]
For edge cases, Thymeleaf supports checkboxes in multi-valued fields by iterating over options with th:each, such as <input type="checkbox" th:field="*{roles}" th:value="${role.name}" />, where roles is a collection on the object; checked states are preserved based on the bound values. Select elements use th:field="*{country}" on the <select> tag, populated with <option th:each="c : ${countries}" th:value="${c.code}" th:text="${c.name}">, automatically selecting the matching value. File uploads require enctype="multipart/form-data" on the form; use <input type="file" name="profileImage" /> and handle via @RequestParam("profileImage") MultipartFile profileImage in the controller, as file fields are processed separately from model attribute binding.[21][22]
Advanced Topics
Custom Dialects
Thymeleaf allows developers to extend its functionality through custom dialects, which enable the creation of domain-specific attributes and elements tailored to application needs. A dialect is a set of processors that define custom behaviors, such as processing specific attributes or tags during template rendering. By implementing custom dialects, users can integrate specialized logic without altering core Thymeleaf components, making it suitable for complex templating requirements in Java-based web applications.[23]
The architecture of a custom dialect in Thymeleaf revolves around implementing the IDialect interface, typically by extending the AbstractProcessorDialect class. This abstract class requires specifying a dialect name, prefix (e.g., "custom"), and precedence level to determine processing order relative to standard dialects. Within the dialect, custom processors—such as attribute processors, element processors, or model processors—are registered to handle specific template features. Attribute processors, for instance, extend AbstractAttributeTagProcessor and target attributes like "custom:attr", parsing their values and modifying the template structure accordingly.[23]
Creating processors involves overriding the doProcess method to execute logic on matched elements. For example, a custom attribute processor for a "th:math" attribute could evaluate mathematical expressions using Thymeleaf's expression language, which in Spring-integrated environments leverages the Spring Expression Language (SpEL) for calculations. The following code snippet illustrates a basic implementation:
java
public class MathAttributeTagProcessor extends AbstractAttributeTagProcessor {
public MathAttributeTagProcessor(final String dialectPrefix) {
super(TemplateMode.HTML, dialectPrefix, null, false, "math", true, 1000, true);
}
@Override
protected void doProcess(final ITemplateContext context, final IProcessableElementTag tag,
final AttributeName attributeName, final String attributeValue,
final IElementTagStructureHandler structureHandler) {
final IExpression expression = StandardExpressions.getExpressionParser(context.getConfiguration())
.parseExpression(context, attributeValue);
final Object result = expression.execute(context);
structureHandler.setAttribute("data-result", result.toString());
}
}
public class MathAttributeTagProcessor extends AbstractAttributeTagProcessor {
public MathAttributeTagProcessor(final String dialectPrefix) {
super(TemplateMode.HTML, dialectPrefix, null, false, "math", true, 1000, true);
}
@Override
protected void doProcess(final ITemplateContext context, final IProcessableElementTag tag,
final AttributeName attributeName, final String attributeValue,
final IElementTagStructureHandler structureHandler) {
final IExpression expression = StandardExpressions.getExpressionParser(context.getConfiguration())
.parseExpression(context, attributeValue);
final Object result = expression.execute(context);
structureHandler.setAttribute("data-result", result.toString());
}
}
This processor parses the attribute value as an expression, computes the result, and sets it as a data attribute on the element. Thymeleaf's expression language serves as the base for such custom evaluations, allowing integration with variables and functions defined in the template context.[23][13]
To register a custom dialect, instantiate it and add it to the TemplateEngine configuration, such as a SpringTemplateEngine in Spring Boot applications. For example:
java
final SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.addDialect(new CustomDialect("custom", "customPrefix", StandardDialect.PROCESSOR_PRECEDENCE));
final SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.addDialect(new CustomDialect("custom", "customPrefix", StandardDialect.PROCESSOR_PRECEDENCE));
This ensures the dialect's processors are applied during template processing, with the prefix qualifying custom attributes (e.g., "custom:math"). Registration can occur programmatically or via configuration classes, enabling modular extension of Thymeleaf's capabilities.[23]
Use cases for custom dialects include developing domain-specific tags, such as a "th:chart" attribute for integrating data visualization libraries like Chart.js. This processor could extract data from the template context, generate JSON configurations via SpEL, and inject script elements to render charts dynamically. Such extensions are particularly valuable in enterprise applications requiring bespoke templating for reporting or dashboards.[23][13]
Best practices for custom dialects emphasize avoiding conflicts with standard attributes by selecting unique prefixes and high precedence values (e.g., 10000) to control execution order. Processors should handle edge cases, such as invalid expressions, and support internationalization through message resolution. Additionally, testing dialects in isolation ensures compatibility with Thymeleaf's caching and rendering pipeline.[23]
Thymeleaf achieves efficient template processing through its event-based architecture, which parses templates into reusable event sequences to minimize repeated I/O operations during rendering.[24] In production environments, performance is further enhanced by enabling caching mechanisms that store parsed templates, expressions, and fragments, reducing CPU and memory overhead for high-traffic applications.[24]
Template Caching
Template caching is a core feature in Thymeleaf, enabled by default on production-mode ITemplateResolver instances via the setCacheable(true) configuration.[25] This caches parsed templates as sequences of DOM events, allowing subsequent renders to bypass file parsing and achieve near-constant time resolution for static templates.[24] Cache managers, such as the built-in StandardCacheManager, support least-recently-used (LRU) eviction policies, with customizable maximum size (e.g., setTemplateCacheMaxSize(100)) to balance memory usage.[24] Time-to-live (TTL) settings control cache freshness; for instance, setCacheTTLMs(3600000L) sets a one-hour expiration, preventing stale content while minimizing reloads.[25] In Spring Boot applications, this integrates seamlessly with the auto-configured SpringTemplateEngine, but developers must ensure caching remains active in non-development profiles to avoid performance degradation.[6]
Expression Pre-compilation
For scenarios with complex or repeated expressions, Thymeleaf supports pre-compilation using the double-underscore syntax, such as __${[user](/page/User).name.toUpperCase()}__, which processes the expression at template parsing time rather than runtime.[26] This shifts computation to the initial cache population, benefiting high-load applications where expressions are evaluated frequently, like in loops or conditional blocks.[26] Pre-compiled expressions are stored in the cache alongside the template, ensuring zero runtime overhead for subsequent renders, though they increase initial parsing time and are unsuitable for dynamic data.[26]
Fragment Caching
Thymeleaf fragments, defined with th:fragment, can leverage external caching via integration with Spring's caching abstraction, such as Ehcache, to store rendered fragment outputs independently of full templates.[27] By implementing a custom ICache backed by @Cacheable annotations and Ehcache as the provider, fragments like navigation menus or sidebars are cached with keys incorporating parameters, reducing recomputation for reusable components.[27] This approach is particularly effective in Spring Boot setups, where the CacheManager handles eviction and TTL, potentially cutting rendering time for fragment-heavy pages by orders of magnitude in cached scenarios.[27]
Thymeleaf integrates with logging frameworks for basic profiling, where enabling the org.thymeleaf.TemplateEngine.TIMER logger at TRACE level outputs detailed timings for each phase of template processing, such as parsing and execution.[28] In Spring Boot environments, this pairs with Actuator's metrics endpoint to monitor aggregate view rendering times via Micrometer, exposing histograms like http.server.requests that include template resolution durations.[29] Custom timers can be added using TemplateEngine.process() wrappers to isolate bottlenecks, aiding in identifying slow expressions or resolvers under load.
Common Pitfalls
A frequent issue arises from disabling caching in production via spring.thymeleaf.cache=false, which forces full template reparsing on every request, leading to exponential slowdowns for large or numerous templates.[30] Overly complex expressions in th:each or th:if attributes can also degrade performance due to repeated evaluations; simplifying expressions where possible mitigates this.[31] Uncached external resources, like message bundles without proper MessageResolver caching, compound delays during internationalization, often inflating render times by 10x or more if locale switching is frequent.[30]