Templating with Thymeleaf: The Basics (Part 1)
Thymeleaf is a modern Java template engine for both web and standalone applications. Although widely used in the Spring ecosystem, it can be integrated into any JVM environment. If you need a dynamic, flexible, and extensible templating engine, Thymeleaf might be just what you need.
In this first part, we’ll explore Thymeleaf’s key features, set up a basic project, and dive into its fundamental syntax and capabilities.
Table of Contents
This is the first article of “Templating with Thymeleaf”, a 3-part miniseries. We’ll cover the basics of setting up Thymeleaf, its fundamental syntax and features in this one. The next article will dive deeper into how to layout with fragments and reusability. And finally, we’ll take a look at custom dialects and more advanced features.
What is Thymeleaf?
At its core, Thymeleaf is a Java-based template engine with a strong focus on HTML/XML. However, it has dedicated modes for JavaScript, CSS, and plain text, making it usable for various content generation tasks.
What sets Thymeleaf apart from other engines is its natural templating philosophy. Unlike other template engines that use custom syntax, Thymeleaf uses normal HTML templates that can be viewed as static files without requiring any processing. This feature is particularly helpful when collaborating with front-end developers or designers who can work with pure HTML/XML prototypes.
This article guides you through using Thymeleaf in a framework-agnostic way. Besides the initial setup, the majority of the content and examples will apply no matter how you choose to use it.
Key Features
Natural templating
Templates remain valid HTML/XML, as standard elements and attributes are used instead of a specialized template syntax.Rich feature set
Internationalization, forms, fragments, layout management, and more.Extensibility
Create custom dialects, which we’ll explore in the final article.Platform-agnostic, but deep Spring integration
Usable without any framework yet works seamlessly in Spring’s MVC architecture, including data binding.
Adding Thymeleaf Dependency
For Spring, the Thymeleaf Starter dependency will auto-configure most things.
In non-Spring environments, we must manually configure Thymeleaf, including the template engine and template resolution.
First, we add the dependency to our project.
For Maven, add the following to our pom.xml
:
For Gradle, add this to your build.gradle
:
This adds the template engine core without any extras or Spring-specific support.
Make sure to check for the latest version on the Thymeleaf website or Maven Central.
Template Resolution and Setup
Before we can start using Thymeleaf, we need to set up the template engine and configure how it resolves templates. This involves two main steps:
- Configuring one or more template resolvers
- Setting up the template engine
Template Resolving
Resolving templates refers to how Thymeleaf locates and loads templates to process.
Template resolvers deal with the following:
- Location: Where a template is stored
- File format:
.html
,.xml
, etc. - Processing mode:
HTML
,RAW
, etc. - Caching: Should it be cached for performance
The template resolver is responsible for locating and reading template files. We don’t have to implement one from scratch, as Thymeleaf provides several implementations.
Here’s a selection:
ClassLoaderTemplateResolver
: Looks for templates stored in the classpathFileTemplateResolver
: A filesystem-based resolverUrlTemplateResolver
: Loads templates from a URL, like an external resourceStringTemplateResolver
: The template being resolved is the template source
For our purposes, we’ll use a ClassLoaderTemplateResolver
:
The exact options available depend on the type of resolver. The
ITemplateResolver
interface, which all resolvers are based on, has no configuration options; most of the mentioned methods are located inAbstractTemplateResolver
.
The setPrefix(String)
and setSuffix(String)
tell the template resolver where to find the templates and what file extension to look for.
The resolver uses them to build the full path to locate the correct template.
Prefix
Defines the directory or location where templates are stored based on the resolver’s default location. For example, if your templates are stored insrc/main/resources/templates
, you would set the prefix astemplates/
for aClassLoaderTemplateResolver
.Suffix
Specifies the file extension of the templates.
When you process a template, Thymeleaf will combine its default location, the prefix, the template name, and the suffix to look for the file:
Next is setTemplateMode(TemplateMode)
, which sets the template mode, which tells the engine how to interpret a template’s content.
The available options are:
HTML
,XML
TEXT
JAVASCRIPT
CSS
RAW
Some template resolvers detect the template mode based on the file extensions and might ignore the explicitly set one.
That’s why the ClassLoaderTemplaterResolver
has setForceTemplateMode(boolean)
to prevent that behavior if needed.
setCharacterEncoding(String)
sets the encoding of the templates; there’s nothing special here.
The setCacheable
option can significantly improve performance.
However, during development, you’d want to turn it off to see any templates changes reflected immediately.
And finally, the setOrder(Integer)
call sets the order of resolvers if more than one is configured.
With the resolver configured, we now create the template engine.
Setting Up the TemplateEngine
With our resolver configured, it’s time to create the TemplateEngine
:
That was easy… if we have more than one resolver, we have multiple options at hand to add them to the engine:
Be aware that the set...
methods remove all previously added/set resolvers on the engine.
More configuration options are available, but that’s out of the scope of this article.
Rendering a Thymeleaf Template
Now that we have the engine set up, we can start processing templates
Here’s a simple example stored as src/main/resources/templates/hello.html
:
Thanks to Thymeleaf’s natural templating approach, any browser renders the template as normal HTML, displaying “Welcome, User!”.
The actual processing magic is done via the non-interfering th
-prefixed attributed.
In this case, the th:text
attribute replaces the content of the <span>
element with the variable name
from the Context object provided to the TemplateEngine
for processing.
In Java code, it looks like this:
Simple enough, all we need is a configured template engine, a template, and a context to fill in the blanks.
Working with Context
The Context in Thymeleaf is a crucial concept. It serves as a container for all the data we want to make available in our template.
Creating and Populating a Context
We’ve already seen a basic example of creating a Context
and adding variables to it.
It stores key-value pairs, where each key is a String
(the variable name) and the associated value can be anything.
Creating a Context
object is straightforward.
You instantiate a new instance and then populate it with variables using the setVariable
method.
Here’s how it works in practice:
There are multiple types of contexts available, like WebContext
, which gives access to session and request parameters.
Let’s dive into the syntax to see how to use those variables!
Basic Syntax and Template Features
A wide range of features are available for manipulating text and attributes, resolving URLs, and implementing conditional logic.
To avoid interfering with HTML/XML, all attributes and elements live in the th
namespace, which has to be declared in each template:
Attributes are parsed by Thymeleaf and evaluated as expressions, which is why variable expansion is used for accessing data from Context
object with the help of the ${ }
syntax.
Thymeleaf’s standalone expression language is explicitly built for its templating needs. It includes features like variable expressions, message expressions, link expressions, and fragment expressions, etc. While it shares some syntactic similarities with other expression languages, it is fully independent and built specifically for Thymeleaf.
Text Manipulation
Dynamically inserting or replacing content in a template is the bread and butter of any templating engine. Of course, Thymeleaf has multiple ways to interact with text.
Replacing Text
The th:text
attribute replaces the content of its element:
Unescaped Text
Thymeleaf escapes any rendered text make the result safe to use.
However, if you need HTML in the generated output, you can use th:utext
to render unescaped text:
Attribute Processors
Thymeleaf provides various processors to manipulate attributes.
Setting Attributes
Setting specific attributes is done with th:attr
:
The
@{ }
syntax resolves URLs dynamically, which we’ll talk about in the next section.
As working with the class
attribute in HTML is such a common use-case, there’s a dedicated attribute:
Prepending/Appending Attributes
Where th:attr
overrides any pre-existing value, its brethren th:attprepend
and th:attrappend
do what’s written on the package:
As with th:attr
, there’s a dedicated attribute for dealing with class
:
URL and Resource Resolution
Working with URLs can be a hassle, especially when dealing with relative URLs to other resources. That’s why using context-aware URLs is particularly useful when:
- Your application is deployed with a context path
- URLs are dynamically generated
- You want to ensure consistency in URL generation
Thymeleaf’s @{ }
syntax handles this by ensuring that URLs are correctly resolved and include the proper context path or dynamic parts as needed.
Basic URL Syntax
URLs are simply wrapped in @{ }
:
If the application has a context path (e.g., /myawesomeapp
), it will automatically be prepended to /profile
.
This requires the context to be a
WebContext
The big advantage of this approach is consistency, even if the context path changes. The resulting URL will always be correct without updating any templates.
Absolute URLs that include a scheme won’t be prepended:
Server-relative URLs
If we want to link to another resource on the server, ignoring a possible context path, we use server-relative URLs.
If a URL starts with ~
(tilde), it won’t be prepended with the current context path:
Protocol-relative URLs
Equivalent to how HTML handles URLs, //
(double-slash) can replace an absolute URL’s scheme to match the one on the current page:
Parameterized URLs
URLs don’t have to be static; they can use variables, either as literals or as expressions.
The data to be used for the URL is appended to it in normal parentheses.
If the URL contains a { }
placeholder, it will fill it with the variable; otherwise, it gets appended as a query parameter:
This will render to:
Instead of using literals, variable interpolation to use values from the template’s Context
is possible:
Conditionals
Several options are available to express conditional logic in our templates.
Thymeleaf supports if
, unless
, and for more complex decisions, switch
.
Simple Conditions: th:if and th:unless
The th:if
and th:unless
attributes render their content their expressions.
Unlike Java, there’s no explicit concept of else
, so the two attributes often complement each other:
There are also and
/or
operators available to improve the conditional’s expression:
Switch Statements: th:switch and th:case
If more than just a simple true
or false
is required for making a rendering decision based on a value, there’s also a th:switch
attribute, which is accompanied by th:case
:
The *
(asterisk) acts as the default cause.
Elvis Operator
For inline conditions, Thymeleaf also supports the Elvis operator ?:
(question mark colon):
Iterating over Collections
One of the most powerful features of Thymeleaf is its ability to iterate over collections of data using the th:each
attribute.
This allows you to dynamically generate repetitive content like lists or tables, based on your data.
Basic Usage
The element with the attribute is rendered for each loop, so we need to add additional attributes or inner elements to do the actual work:
In this example, Thymeleaf loops over items
from the template’s Context
and stores the value in item
.
The th:text
attribute then replaces the <li>
element’s content with item.name
:
Iteration Status Variable
There’s an optional status variable available that gives us additional information about the current iteration.
For example, if we need an index, we can access it via a special variable called iterState
:
As expected, the index
starts at 0
(zero).
There are other properties available as well:
count
: Current iteration index, starting a1
(one)size
: Total amount of elementscurrent
: Current iteration variableeven/odd
: Indicates if the current iteration is even or odd (useful for alternating renders, like striped tables)first/last
: Helps with rendering the first or last element differently
These properties cover many common use-cases encountered when trying to layout nice-looking HTML, as we often need to treat the edges of repeating elements differently.
Conclusion
In this first part of our Thymeleaf series, we’ve explored the basics of Thymeleaf, covering everything from template configuration, basic syntax, and features like text replacement, conditionals, iteration, and URL resolution.
Thymeleaf’s natural templating approach, combined with its versatile, easy-to-grasp features make it an excellent choice for smooth collaboration with non-developers.
Whether you’re building small components or complex systems, mastering these foundational features can help you create dynamic and maintainable templates.
In the next part of this series, we’ll dive deeper into Thymeleaf by exploring how to modularize templates with fragments and more!