Site icon Blog Zenika

Introducing the Thymeleaf template engine

There’s a bunch of Java template engines, but one of them has been getting some momentum these days: Thymeleaf. Nice and powerful syntax, flexibility, vibrant community, and good integration with popular web technologies, these are all good reasons to discover this alternative to JSP. This article lists the core features of Thymeleaf and shows how to write and process an HTML template.

Thymeleaf in a nutshell

Thymeleaf is a Java template engine. It’s an open source project and is licensed under the Apache License 2.0. Here are the core features of Thymeleaf:

Yes, that’s a lot of features! Keep reading to discover how to write and feed a Thymeleaf template with data…

Setup of the template engine

Thymeleaf infrastructure is quite simple: a TemplateResolver to load templates and a TemplateEngine to do the actual processing (merging templates with a given context). Here is the code for a standalone setup:

ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();
resolver.setTemplateMode("XHTML");
resolver.setSuffix(".html");
TemplateEngine engine = new TemplateEngine();
engine.setTemplateResolver(resolver);

We’re going to load the templates from the root of the classpath. When we ask Thymeleaf to render a template called home, the resolver will add the html extension, because we set the suffix property. We’ll be then able to think in terms of logical names and won’t worry about the physical name of the file.

The template

Our template is a home.html file located at the root of the classpath (e.g. in the src/main/resources directory of the project if we follow Maven conventions). The first version of the template contains only the skeleton an HTML page:

<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-3.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <title>My first template with Thymeleaf</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
</body>
</html>

Note the use of a Thymeleaf-specific doctype.

Processing of the template

There’s nothing dynamic in our template, but we can test our setup by adding the following code after the engine configuration:

StringWriter writer = new StringWriter();
Context context = new Context();
engine.process("home", context, writer);

The writer variable should then contain the output of the processed template, which is basically the static content of the file. Things that matter:

The processing needs a context. This object will typically contain variables we want to display in the view.

The template to render is called home. The resolver will be in charge of mapping this logical name with the physical location of the file. Remember we’re using a classpath-based resolver which adds an HTML extension to the template name.

OK, everything seems to work! See how Thymeleaf is lightweight. The template engine will be most of the time used in a web environment, but we can also easily use it in a standalone environment, like a JUnit test.

Labels and internationalization (i18n)

Thymeleaf’s default internationalization support is quite simple: drop a properties file beside your template and you’re done. Let’s create a home.properties in the same directory as our template:

hello.world=Hello World!

And a home_fr.properties file for the French version:

hello.world=Bonjour le monde !

We modify our template to refer to this label:

<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-3.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <title>My first template with Thymeleaf</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
    <p th:text="#{hello.world}">Hello</p>
</body>
</html>

That’s it, we unveiled how we insert dynamic content in Thymeleaf’s templates: by placing extra attributes in HTML elements:

<p th:text="#{hello.world}">Hello</p>

The nested Hello is there just for a preview, Thymeleaf will replace it by the dynamic value during processing. Note the use of a Thymeleaf’s specific th:text attribute and the use of the #{key} syntax to refer to an entry of the property file.
If we execute the rendering program, we end up with the following output (if your locale is something else than French!):

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>My first template with Thymeleaf</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
    <p>Hello World!</p>
</body>
</html>

What about the French version? We can specify the locale in the context:

StringWriter writer = new StringWriter();
Context context = new Context(Locale.FRANCE);
engine.process("home", context, writer);

And Thymeleaf uses the appropriate properties file:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>My first template with Thymeleaf</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
    <p>Bonjour le monde !</p>
</body>
</html>

So far, so good, let’s move on to the core topic: pushing objects in the context to render them in the view.

Variable substitution

Let’s imagine we want to display the current date in the view. We can feed the context with an already-formatted string:

String now = new SimpleDateFormat("yyyy-MM-dd").format(Calendar.getInstance().getTime());
context.setVariable("date", now);

And then refer to this variable in the template like the following:

<p th:text="${date}">The date</p>

Note the use of the ${variable} syntax this time (we used #{...} for i18n). We’re again relying on the th:text attribute to inject dynamic content. If we process the template again, the output contains the expected content:

<p>2013-01-18</p>

Let’s see how to display a typical domain object.

Variable substitution with a Java bean

Displaying domain objects is a common use case of web applications. We can add a Contact object for display to the Thymeleaf context:

context.setVariable("contact",new Contact("John","Doe"));

We hard-coded the domain object state, but it could be as well loaded from a database. Here’s how to display the firstname and lastname properties of the domain object:

<div>
     <p>First name: <span th:text="${contact.firstname}">First name</span></p>
     <p>Last name: <span th:text="${contact.lastname}">Last name</span></p>
</div>

As you can see, the content of ${...} in Thymeleaf can be a complex expression, not only the reference to variable. Thymeleaf uses OGNL for its default processing engine, opening a wide range of possibilities: operators, concatenation, etc.
Let’s see now an extra feature that can make the display of Java object shorter.

Less verbosity with the selection syntax

Thymeleaf allows to perform a selection on objects. Once an object has been selected, it’s available as some kind of first-level variable in the evaluation context. We can then refer to it using the *{...} syntax instead of the ${...} syntax.
In our template, we can select the contact object with the usual ${...} syntax and then refer to its properties with the *{...} syntax:

<div th:object="${contact}">
    <p>First name: <span th:text="*{firstname}">First name</span></p>
    <p>Last name: <span th:text="*{lastname}">Last name</span></p>
</div>

We end up with the exact same rendering, but the template code is more simple. Nice, isn’t it?

Iteration

Another common use case of web applications is the display of data in tables: we load a list of Java objects from the database and display them in a HTML table. Imagine we feed our context with a List<Contact>:

Context context = new Context();
List<Contact> contacts = new ArrayList<Contact>();
contacts.add(new Contact("John","Doe"));
contacts.add(new Contact("Jane","Doe"));
context.setVariable("contacts",contacts);
engine.process("home", context, writer);

The iterate over the list, we use the th:each attribute:

<table>
    <tr>
        <th>Firstname</th>
        <th>Lastname</th>
    </tr>
    <tr th:each="contact : ${contacts}">
         <td th:text="${contact.lastname}">The first name</td>
         <td th:text="${contact.firstname}">The last name</td>
     </tr>
</table>

We end up with this output:

<table>
    <tr>
        <th rowspan="1" colspan="1">Firstname</th>
        <th rowspan="1" colspan="1">Lastname</th>
    </tr>
    <tr>
         <td rowspan="1" colspan="1">Doe</td>
         <td rowspan="1" colspan="1">John</td>
    </tr><tr>
         <td rowspan="1" colspan="1">Doe</td>
         <td rowspan="1" colspan="1">Jane</td>
    </tr>
</table>

 
Note Thymeleaf added some rowspan and colspan attributes in accordance with the DTD for the selected XHTML 1.0 Strict standard. Thymeleaf wouldn’t generate them if we have told it to be compliant to HTML 5.
Before finishing our discovery of Thymeleaf, let’s play with conditional statements.

Conditional, « if » syntax

Imagine we don’t want to display an empty table if the list of contacts is empty, we can easily check the size of the list and choose to display the whole table o
nly if the list isn’t empty:

<table th:if="${not #lists.isEmpty(contacts)}">
    <tr>
        <th>Firstname</th>
        <th>Lastname</th>
    </tr>
    <tr th:each="contact : ${contacts}">
         <td th:text="${contact.lastname}">The first name</td>
         <td th:text="${contact.firstname}">The last name</td>
     </tr>
</table>

 
Note the use of the #lists utility objects to check whether the list is empty or not.

Conclusion

This article introduced the basic templating features of Thymeleaf. Thymeleaf is meant to be used in web applications, but its flexibility let us use it in a standalone environment. Thymeleaf doesn’t need a JSP compiler and can fetch templates from anywhere (file system, web context, classpath). You can even package your whole web application in a simple JAR, that’s a great step towards modularity! Thymeleaf has tons of features and we’ll see how to integrate it with Spring MVC in a subsequent post.
Source code

Quitter la version mobile