Using Thymeleaf Fragments with HTMX

05 Jun 2024

A follow-up to this note can be found here: Thymeleaf and HTMX - More Thoughts.

Code to accompany this note is available in GitHub.

One of the goals of a web library such as HTMX is that it expects Ajax responses typically to be HTML (and not, for example, JSON). This has the potential to significantly reduce the amount of web page JavaScript you may need to merge a JSON response into the HTML in which that data is displayed.

Thymeleaf generates HTML from templates. This happens on the server. So it appears to be a good fit for generating the HTML responses expected by HTMX. Thymeleaf also supports fragments - separate pieces of HTML which can be incorporated into full Thymeleaf templates to create pages from components. Again, this seems well-suited to how HTMX works.

However, one wrinkle is that Thymeleaf fragments cannot be rendered in the same direct way as full Thymeleaf pages - especially when using a web framework such as Javalin.

Is there a reasonable way to work around this?

Javalin supports various rendering engines including Thymeleaf.

It can be configured as simply as this:

Java
1
2
3
Javalin.create(config -> {
    config.fileRenderer(new JavalinThymeleaf());
});

And then used like this by Javalin:

Java
1
ctx.render("myThymeleafTemplate.html", model("firstName", "John", "lastName", "Doe"));

As well as ctx.render(...), Javalin also provides ctx.html("a string of html").

But how do we create this string of HTML - especially if we want to pick a fragment from a Thymeleaf file containing multiple fragments? (We want to avoid a proliferation of HTML files - one file per Thymeleaf fragment!).

In the following example, we see the entire contents of a Thymeleaf file called messages.html:

HTML
1
2
<div th:fragment="msg_one" th:text="|one: ${msg1} from ${foo}|"></div>
<div th:fragment="msg_two" th:text="|two: ${msg2} from anonymous|"></div>

It contains two fragments named msg_one and msg_two. There may be many such fragments needed in our web application to handle all the Ajax calls we want to make, and as mentioned we want to avoid the need to create a separate Thymeleaf file for each fragment.

Thymeleaf’s template engine org.thymeleaf.TemplateEngine contains a method with the following signature:

Java
1
String htmlString = process(String templateName, Set fragmentNames, Context context)

This supports specifying the template’s file name, plus a set of (one or more) fragment names, plus a Thymeleaf context (which is essentially just the model data needed to populate our Thymeleaf fragment’s variables).

Unfortunately, using this in Javalin does require a bit of extra code, to ensure we have access to the Thymeleaf TemplateEngine. But it’s not too bad, given the potential savings we get by avoiding a potentially larger amount of JSON and JavaScript.

A working (albeit extremely basic) example is provided in GitHub.

The code for handling fragments is in ThymeleafFragmentHandler, which is passed the TemplateEngine instance from the program’s main method.

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
private void renderFragment(Context ctx, String template, Set fragments, Map model) {
    ctx.html(te.process(
            template,
            fragments,
            new org.thymeleaf.context.Context(
                    locale,
                    model
            )
    ));
}

The Javalin ctx.html() method consumes the rendered string of HTML created by te.process().

This is sent to the web page as an Ajax response to a HTMX tag such as this one which specifies an Ajax get request:

HTML
1
2
3
<div hx-get="/message_one">
    Click here for message one.
</div>

For an alternative approach, which uses j2html instead of Thymeleaf, see the article “Full stack web development in a single Java file” by Anthony Bruno.