Tobias Erdle's Blog

Development Engineer working with different technologies. Former Jakarta MVC & Eclipse Krazo Committer. All views are my own.

Github: erdlet | E-Mail: blog (at) erdlet (punkt) de

Chaining Thymeleaf template resolvers

TL;DR

To chain template resolvers in Thymeleaf, you need to set the order via AbstractTemplateResolver#setOrder for each template resolver and check for existence with AbstractTemplateResolver#setCheckExistence in the leading resolvers to allow a fallthrough to following ones, handling the not resolvable template. Then add them to the TemplateEngine with TemplateEngine#addTemplateResolver.

Initial situation

Today I tried to fix an issue in the Krazo Thymeleaf Extension which drives my crazy everytime I use this extension, namely that using fragments forces me to add the whole path of the template located in /WEB-INF/views (or wherever the Jakarta MVC application is configured to search for views). So I started to debug the issue using this easy templates where index.html is the view and fragment.html contains a simple HTML fragment to include.

<!-- index.html -->
<html  xmlns:th="http://www.thymeleaf.org">
    <head>
      <title>Thymeleaf default view folder test</title>
    </head>
    <body>
    <div th:replace="fragment :: test"></div>
    </body>
</html>
<!-- fragment.html -->
<div th:fragment="test" id="from-fragment">I'm from the fragment</div>

The currently used template engine was produced with a single org.thymeleaf.templateresolver.WebApplicationTemplateResolver using its default config. Now I started to debug and recognized, that the view returned by the controller could be resolved because it contained the full path when fetched from the jakarta.mvc.engine.ViewEngineContext. On the other hand, the fragment was only resolved as /fragment which surely can't be found in this location when it is really located in /WEB-INF/views/. So I tried a few things until I came to a positive result.

Using prefixes / suffixes in default template resolver

My first approach was to use the setPrefix and setSuffix method of WebApplicationTemplateResolver to set the required values. My expectation was, that the resolver recognizes that /WEB-INF/views/index.html has the correct format and does nothing, whereas fragments does not and needs them applied. Unfortunately, with this approach the template resolver added prefix and suffix even to the /WEB-INF/views/index.html so the path got wrong and not resolvable. After this failure I tried another approach, testing if a second template resolver may help.

Using a second template resolver

So after I couldn't implement my desired behavior with a single template resolver, I added a second one which got configured like this:

        final String mvcViewPath = //...

        final WebApplicationTemplateResolver secondResolver = new WebApplicationTemplateResolver(servletApplication);
        secondResolver.setPrefix(mvcViewPath);
        secondResolver.setSuffix(FILE_SUFFIX);
        secondResolver.setTemplateMode(TemplateMode.HTML);
        secondResolver.setCharacterEncoding(StandardCharsets.UTF_8.name());
        secondResolver.setOrder(2);

        //...

Here I set the views directory as a prefix and .html as suffix, so fragment is, assumed that mvcViewPath evaluates to /WEB-INF/views, transformed to /WEB-INF/views/fragment.html. Also I set the order of the template resolver to ensure, that the default one runs first, which got the order 1. Afterwards I added the initialized engine also to the TemplateEngine via TemplateEngine#addTemplateResolver and tried again. Unfortunately, it failed again.

The solution: additionally use WebApplicationTemplateResolver#setCheckExistence

I checked the available methods a few times until I came upon setCheckExistence which contains this interesting sentence in its JavaDoc:

This allows resolvers to pass control to the next ITemplateResolver in the chain

So what I simply missed is to set this flag at the first template resolver, so in case the original fragment template is not found, the name will be checked in the next template resolver which adds the required prefix and suffix and will be able to find the template.

Attention: I just used two template resolvers here. In case you have more of them, all but the last one need this flag set to true.