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:

  • Simple and natural templating
  • Optimized for web environment but can work standalone
  • Advanced evaluation language (OGNL or Spring Expression Language)
  • Support for template logic (condition, iteration)
  • Full support for internationalization
  • Spring MVC and Spring Web Flow integration
  • Support for the composite view pattern (native with fragments, Tiles integration, usable with Sitemesh)
  • Sophisticated template caching support
  • Works with Dandelion-Datatables

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 only 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


Commentaires

1. Le vendredi 25 janvier 2013, 15:28 par Angelo

Many thank's Arnaud for your article. I didn't know this template engine framework. The most popular Java template engine ar Freemarker and Velocity. So when I read your article, I told me why choosing thymeleaf instead of using Freemarker or Velocity? There are an answer at http://www.thymeleaf.org/faq.html#c...

I think too tooling is very important for a language and it seems that it exists Eclipse plugin for thymeleaf at
https://github.com/ultraq/thymeleaf...

Very interesting project, thank's to have shared it.

Regards Angelo

2. Le vendredi 25 janvier 2013, 16:47 par Seb

Thanks for this nice intro to a great tool !
Thymeleaf is a good framework due to it's simplicity, enforcing a clear separation of the "M" and the "V"
Another approach can consist in sticking to a pure HTML template, then inject data and logic into it with a tool like Moulder-J (https://github.com/jawher/moulder-j)

3. Le dimanche 27 janvier 2013, 02:33 par Daniel Fernández

Thanks for your post! I've just linked it from the Thymeleaf home page ( http://www.thymeleaf.org/index.html ) and also as an external link at the project's documentation page ( http://www.thymeleaf.org/documentat... ).

4. Le lundi 28 janvier 2013, 10:35 par jpalies

Thanks for the post! One small thing though: your link to your previous article (which was nice too) doesn't work on firefox (at least, but seeing the differences I reckon it does not work at all). Your current link is http://blog.zenika.com/index.php?po... and should be http://blog.zenika.com/index.php?po...

5. Le lundi 28 janvier 2013, 16:17 par Martin

Thanks Arnauld, I decided on tyhmeleaf as well because of its flexibility and ease of use, however would have liked a bit more on DTD and XML, found hs instead, maybe youre readers woula appreciate it a swell, http://www.liquid-technologies.com/...

thanks again

6. Le mercredi 30 janvier 2013, 10:56 par Arnaud Cogoluègnes

@Angelo: I'm glad you discovered this framework thanks to the post. Enjoy!
@Daniel: thanks for the link on the Thymeleaf website! Keep up the good work!

Fil des commentaires de ce billet

Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.