Thymeleaf

Thymeleaf 3.1: What’s new and how to migrate

Latest version is Thymeleaf 3.1.1.RELEASE.

What’s new

Support for Servlet API 5.0 and the jakarta.* class namespace

Thymeleaf 3.1 adds support for the new jakarta.* class namespace in the Servlet API since version 5.0, without removing support for the javax.* classes in previous versions.

Support for Spring 6.0

Thymeleaf 3.1 adds a new thymeleaf-spring6 core library for integration with Spring Framework 6.0.

Support for versions of Spring older than Spring 5.0 has been removed.

Support for Spring Security 6.0

Thymeleaf 3.1 adds a new thymeleaf-extras-springsecurity6 core library for integration with Spring Security 6.0.

Support for versions of Spring Security older than Spring Security 5.0 has been removed.

Core support for the java.time package

The thymeleaf-extras-java8time extras module has been integrated into the Thymeleaf core: the #temporals expression utility object is now always available.

Java compatibility

JDK 8 is now the minimum generally required version.

JDK 17 is the minimum required version for the thymeleaf-spring6 and thymeleaf-extras-springsecurity6 core libraries.

Removal of web-API based expression utility objects

The #request, #response, #session, and #servletContext are no longer available to expressions in Thymeleaf 3.1.

Tighter restrictions on the use of classes in expressions

Thymeleaf 3.1 establishes a general restriction on the use of classes from core packages: java.*, javax.*, jakarta.*, jdk.*, org.ietf.jgss.*, org.omg.*, org.w3c.dom.*, org.xml.sax.*, com.sun.* and sun.*.

Method/constructor calling is now forbidden for classes in these packages, as well as static references.

As an exception to this restriction, some classes in these packages are always allowed:

  • Basic java.lang.* and java.math.* classes: java.lang.Boolean, java.lang.Byte, java.lang.Character, java.lang.Double, java.lang.Enum, java.lang.Float, java.lang.Integer, java.lang.Long, java.lang.Math, java.lang.Number, java.lang.Short, java.lang.String, java.math.BigDecimal, java.math.BigInteger, java.math.RoundingMode.

  • Collection classes and interfaces: java.util.Collection, java.util.Enumeration, java.util.Iterable, java.util.Iterator, java.util.List, java.util.ArrayList, java.util.LinkedList, java.util.Set, java.util.HashSet, java.util.LinkedHashSet, java.util.Map, java.util.Map.Entry, java.util.HashMap, java.util.LinkedHashMap. Note: interface methods (e.g. Map#get(key)) are commonly allowed for any implementation, but the specific implementations listed here can additionally be constructed and statically referenced.

  • Other commonly used classes in java.util.*: java.util.Properties, java.util.Optional, java.util.stream.Stream, java.util.Locale, java.util.Date, java.util.Calendar.

Deprecation of some artifacts and removal of previously deprecated ones

Some artifacts have been deprecated in Thymeleaf 3.1.

  • Deprecated th:include in favour of th:insert. Note that th:insert has slightly different semantics to th:include.
  • Deprecated unwrapped syntax for fragment insertion: now ~{template :: fragment} should always be used instead of simply template :: fragment.

Also, artifacts previously deprecated in 3.0 have been removed:

  • Removed th:substituteby, deprecated previously in favour of th:replace.
  • Removed deprecated use of execInfo as a context variable (${execInfo}), available since 3.0 as an expression utility object (${#execInfo}).

Other minor improvements

  • General update of dependency versions.
  • Allow #temporals expression utility object to format temporals in non-default locales.
  • Support iterating (e.g. th:each) directly on java streams (java.util.stream.Stream).
  • Allow SpringTemplateEngine instances to be configured a custom (even non-Spring) message resolver.

(For developers) Total overhaul of the project source repository structure

Thymeleaf 3.1 includes a total overhaul of the (previously multiple) source repositories and big improvements in how example applications are dealt with from a development perspective:

  • Integration of most previous Thymeleaf code repositories into the thymeleaf GitHub repository, which now contains:
    • The new Thymeleaf BOM (thymeleaf-parent) integrating and unifying all thymeleaf dependencies and build configuration.
    • All Thymeleaf core libraries, including Spring and Spring Security integrations.
    • All Thymeleaf testing libraries and their integrations.
    • All Thymeleaf test repositories.
    • All Thymeleaf official example applications, including core, Spring, Spring Security and Spring Boot based example apps.
  • Creation of a large Maven multiproject configuration for building the complete tree of Thymeleaf modules.
  • Configuration of example applications in order to allow non-Spring Boot based web apps to be executed from the Maven command line.
  • Creation of more complete distribution packages in .zip form, now including not only libraries but also example applications both in binary and (buildable) source form.
  • Migration of all the testing infrastructure to JUnit 5.

Migrating to Thymeleaf 3.1

JDK version

Thymeleaf 3.1 moves its minimum compatibility level to JDK 8, but the thymeleaf-spring6 and thymeleaf-extras-springsecurity6 require JDK 17 because this is the version of the JDK required by Spring 6.0.

Spring 6.0 and Spring Security 6.0 (and Spring Boot 3.0)

Thymeleaf’s new integrations with Spring 6.0 and Spring Security 6.0 are configured in an equivalent way as they were (and still are) for Spring 5.x.

No changes should be needed except for replacing the previous thymeleaf-spring5 or thymeleaf-extras-springsecurity5 dependencies with the new thymeleaf-spring6 or thymeleaf-extras-springsecurity6 ones.

In the case of Spring Boot bases applications, no changes are needed. The new Spring Boot 3.0 will already configure and use Thymeleaf 3.1 when adding the Thymeleaf Spring Boot starter.

Expression restrictions

In order to improve the security of your templates, Thymeleaf 3.1 has adopted a series of restrictions on variable expressions (${...} and *{...}) that might affect your existing code.

As explained in the “What’s new” section, expression utility objects #request, #response, #session and #servletContext are no longer available from expressions in templates.

The recommended alternative would be to add to your model, at the controller level, the specific pieces of information your templates need from these objects. This can be done via model#addAttribute(...) in the controller code, or via @ModelAttribute or even @ControllerAdvice annotations.

@ModelAttribute("contextPath")
public String contextPath(final HttpServletRequest request) {
    return request.getContextPath();
}

But besides this, as also detailed in the “What’s new” section, a hard usage restriction has been established on classes belonging to the JDK and Jakarta EE core classes, with some exceptions. Objects of the forbidden classes will not be usable in variable expressions since Thymeleaf 3.1.

If some of your templates absolutely need to execute expressions on objects of the forbidden classes, you can create a wrapper class (in your own application’s package) that delegates its methods to the original object, and that will be usable from variable expressions.

Deprecation of th:include

If your templates make use of the th:include attribute, please note that this will still be allowed in Thymeleaf 3.1 but will be removed in a future version of the library. It is strongly recommended that you replace your uses of th:include with th:insert, but noting that they do not work in exactly the same way.

Whereas th:include only inserted the contents of a fragment, making this:

<div id="main" th:include="~{::frag}">...</div>
...
<p th:fragment="frag" class="content">
    something
</p>

…result in this:

<div id="main">
      something
</div>

Using th:insert, the whole fragment including the tag it is defined on will be inserted, making this:

<div id="main" th:insert="~{::frag}">...</div>
...
<p th:fragment="frag" class="content">
    something
</p>

…result in this:

<div id="main">
  <p class="content">
      something
  </p>
</div>

If you specifically needed to achieve the same result as with th:include, you will need to combine th:insert and th:remove in a way similar to:

<div id="main" th:insert="~{::frag}">...</div>
...
<p th:fragment="frag" th:remove="tag" class="content">
    something
</p>

…which will result in:

<div id="main">
      something
</div>

Remember also that fragments can also be defined using the <th:block> tag which will always disappear after evaluation, providing higher flexibility:

<div id="main" th:insert="~{::frag}">...</div>
...
<th:block th:fragment="frag">
    something
</th:block>

The result would be:

<div id="main">
      something
</div>

Deprecation of unwrapped fragment expressions

Fragment expressions in Thymeleaf are expressed like ~{...}, and they can be used in many kinds of attributes and expressions, though they typically appear as the values for th:insert and th:replace attributes.

Before Thymeleaf 3.1, attributes such th:insert or th:replace (or the deprecated th:include) allowed the specification of fragment expressions without the ~{...} envelope:

<div id="top" th:insert="common :: header">...</div>

But since Thymeleaf 3.1 this syntax, though it will still work, will be considered deprecated and scheduled to be removed in a future version. The above should now be expressed like:

<div id="top" th:insert="~{common :: header}">...</div>