Upgrading Thymeleaf 3.0 to 3.1

03 Nov 2022

I recently upgraded a project from Javalin 4 to Javalin 5. One of the significant changes there is the upgrade of its embedded Jetty server:

Jetty 9 (what Javalin has been running on since 2017) has been end-of-lifed, and later Jetty versions require Java 11. Jetty 11 is the latest stable version of Jetty.

This Jetty version change brings with it a move from using the javax namespace to using jakarta. This is fine, except that Thymeleaf version 3.0 uses javax in a few places, and so it is not compatible with this change.

Now, with Javalin 5 you will see the following exception if you try to render a Thymeleaf 3.0 template. Something like this:

1
2
3
4
5
6
7
[JettyServerThreadPool-39] ERROR io.javalin.Javalin - Exception occurred while servicing http-request
java.lang.NoClassDefFoundError: org/thymeleaf/web/servlet/JakartaServletWebApplication
	at io.javalin.rendering.template.JavalinThymeleaf.render(JavalinThymeleaf.kt:25)
	at io.javalin.rendering.JavalinRenderer.renderBasedOnExtension(JavalinRenderer.kt:21)
	at io.javalin.http.Context.render(Context.kt:433)
	at io.javalin.http.Context.render(Context.kt:436)
  ...

The solution is to use Thymeleaf 3.1 - for example, as of writing, that would be:

XML
1
2
3
4
5
<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf</artifactId>
    <version>3.1.0.RC1</version>
</dependency>

And in my Javalin application, where I use my own Thymeleaf renderer:

Java
1
JavalinThymeleaf.init(MyTemplateEngine.configureEngine());

This returns a org.thymeleaf.TemplateEngine instance.

After performing this Thymeleaf upgrade (for Javalin or any other reason), you may start to see the following deprecation warning:

1
2
3
[THYMELEAF][qtp1811787796-55][/record/login.html]
Deprecated unwrapped fragment expression...
Please use the complete syntax of fragment expressions instead...

This deprecation has been planned for a long time - and has now arrived.

Basically, fixing the warning simply involves wrapping your existing unwrapped fragment expression inside ~{ ... }.

For example, this:

HTML
1
2
3
4
5
6
<div th:fragment="mytitle"
    th:replace="/fragments/fragments.html :: title-row(
        ${myTitle}
    )">
    No Title
</div>

becomes this:

HTML
1
2
3
4
5
6
<div th:fragment="mytitle"
    th:replace="~{/fragments/fragments.html :: title-row(
        ${myTitle}
    )}">
    No Title
</div>

The problem for me was I had many of these deprecations - and it would have been tedious, error-prone and time-consuming to fix them all by hand, across numerous Thymeleaf template files.

Fortunately almost all of them followed the structure shown above. My quick fix was to use the built-in support in Notepad++ for regular expressions. I performed a global replacement across multiple files at once:

The regular expression shown above is:

(\/fragments\/.*?)(">)

And the replacement expression is:

~{$1}$2

The regular expression also needs to interpret . as a newline match, in my case (since my expressions are spread across multiple lines) - hence that option is selected in the Notepad screenshot.

For the replacement expression, $1 and $2 refer to the matching groups in the regular expression - as defined by the (...) parentheses. So, here we see how we are simply adding ~{ in front of the first group, and then adding } in front of the second group.

(I did perform a lot of testing before unleashing this on my Thymeleaf template files.)