Introduction & Architecture

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Thymeleaf 3.1.5.RELEASE (published on 2026-04-21, the latest release on 2026-10-07 when the facts and examples below were checked), Spring Boot 4.1.1 with Spring Framework 7.0.9, Java 21 or later, Layout Dialect 4.0.1, htmx 2.0.x and htmx-spring-boot 5.2.0. The concepts apply to Thymeleaf standalone, with Spring MVC and with Spring WebFlux; where one of them behaves differently the text says so. The examples were executed in a scratch Spring Boot 4.1.1 project on Java 21; each one is followed by the official page it derives from. The page explains what Thymeleaf is made of and where it fits, before the rest of the section builds the Bookshelf — a catalogue of books, authors, reviews and loans rendered on the server with Spring MVC and Thymeleaf — one concept at a time.

What Thymeleaf Is

The official tutorial describes Thymeleaf as a modern server-side Java template engine for both web and standalone environments, capable of processing HTML, XML, JavaScript, CSS and plain text. Three consequences follow from that sentence.

  • It is a template engine, not a web framework. It receives a template name and a set of variables and writes text. Routing, request binding, validation, security and persistence come from the host framework, normally Spring MVC or Spring WebFlux through Spring Boot.

  • It is server-side. The template is evaluated on the JVM before anything reaches the browser, so the browser receives finished markup and never sees the th:* attributes.

  • It runs in standalone programs too. The same engine renders HTML pages, e-mail bodies, reports or generated source code without a servlet container, which the next pages of this section use for configuration (Standalone Engine & Configuration) and e-mails (Emails & Text Templates).

Thymeleaf is free software under the Apache License 2.0, and its FAQ describes it as a complete substitute for JSP and JSTL. Its goal, in the words of the tutorial, is an elegant and highly maintainable way of creating templates, built on the concept of natural templates.

Natural Templates

A template is natural when the engine’s logic is added in a way that does not stop the file from being used as a design prototype. Most processors of the Standard Dialect are attribute processors, and a browser ignores attributes it does not know, so the file still displays correctly when it is opened directly from disk. The tutorial contrasts it with a JSP, where a tag library element such as <form:inputText .../> is not displayable by a browser.

The example below is a text input with a prototype value that the designer sees and an expression that the server evaluates.

A natural template and what the engine writes
<input type="text" name="title" value="Dune" th:value="${book.title}">

With book.title equal to Neuromancer the engine writes the following. The prototype value is replaced and the th:value attribute disappears:

<input type="text" name="title" value="Neuromancer">
A natural template opened as a static file, showing its prototype text, and rendered by the server, showing model data

Natural templating matters for two reasons. Designers and developers can work on the same file, so the cost of turning a static prototype into a working page is small. And the template stays honest: a page that renders without the server is much easier to review, restyle and test in a browser. The official article Thymeleaf vs. JSP in Spring MVC builds the same form with both technologies and makes the same point: a JSP cannot be previewed without a running, deployed server, a Thymeleaf template can.

Natural templating is a property of how you write the template, not an absolute guarantee. Using th:utext, inlined expressions such as [[${title}]] in text, or fragments that only make sense inside a layout makes the raw file less presentable. Later pages of this section (Natural Templates & Decoupled Logic) show the trade-offs, including the th:remove and prototype-only comment blocks that keep a prototype tidy.

The Six Template Modes

Out of the box Thymeleaf processes six kinds of templates, each called a template mode: HTML, XML, TEXT, JAVASCRIPT, CSS and RAW. They are the values of the org.thymeleaf.templatemode.TemplateMode enumeration, and a template resolver chooses the mode of every template it resolves.

Mode What it is for Notes

HTML

HTML5, HTML 4 and XHTML input. The default for Thymeleaf-aware resolvers.

No validation and no well-formedness check. The template structure is respected as far as possible in the output.

XML

XML input.

The input must be well formed (no unclosed tags or unquoted attributes) or the parser throws an exception. There is no validation against a DTD or schema.

TEXT

Templates of a non-markup nature such as text e-mails or generated documentation.

Uses a special textual syntax. HTML or XML processed as TEXT is not parsed as markup, so every tag is plain text.

JAVASCRIPT

JavaScript files in the application.

A textual mode with JavaScript-specific integrations such as specialised escaping and natural scripting.

CSS

CSS files in the application.

A textual mode, like JAVASCRIPT.

RAW

Inserting untouched resources.

No processing at all. Thymeleaf code that the inserted resource contains is never executed.

The sketch below renders one small template in every mode, using the same variables. Note how the output depends on the mode: HTML escaping in HTML, XML and TEXT, a serialised string literal in JAVASCRIPT, a plain value in CSS, and nothing evaluated in RAW.

The same variable in each template mode
Context ctx = new Context();
ctx.setVariable("title", "Dune <1965>");
ctx.setVariable("color", "tomato");

StringTemplateResolver resolver = new StringTemplateResolver();
resolver.setTemplateMode(mode);            // one of TemplateMode.values()
TemplateEngine engine = new TemplateEngine();
engine.setTemplateResolver(resolver);

String out = engine.process(template, ctx);
Mode Template Output

HTML

<p th:text="${title}">x</p>

<p>Dune &lt;1965&gt;</p>

XML

<book><title th:text="${title}">x</title></book>

<book><title>Dune &lt;1965&gt;</title></book>

TEXT

Hello [[${title}]]

Hello Dune &lt;1965&gt;

JAVASCRIPT

var title = /*[[${title}]]*/ 'x';

var title = "Dune <1965>";

CSS

h1 { color: /*[[${color}]]*/ black; }

h1 { color: tomato; }

RAW

[[${title}]] <p th:text="${title}">x</p>

the template, unchanged

The table shows the stored template text and the exact output of the run. The JAVASCRIPT and CSS templates stay valid scripts and style sheets when opened without Thymeleaf because the expression sits in a comment followed by a prototype value: the textual modes are natural too, and they are the subject of the Textual Template Modes page.

Dialects, Processors and the Standard Dialects

Thymeleaf is, in the words of the tutorial, extremely extensible, so much that it could be called a template engine framework. Two terms carry the design:

  • A processor is an object that applies some logic to a markup artifact: a tag, a block of text, a comment, or a placeholder when the template is not markup.

  • A dialect is a set of processors, plus perhaps other artifacts, registered under a prefix. A dialect can even have no processors at all.

The Standard Dialect, defined in the core library, provides the attributes and the expression syntax that this whole section teaches: th:text, th:each, th:if, th:href, th:object, th:insert and the rest, together with the five expression types (variable ${...}, selection *{...}, message #{...}, link @{...} and fragment ~{...}). The Spring integration adds the SpringStandard Dialect, which is mostly the same but adapted to Spring. The visible difference is the expression language of ${...} and *{...}:

Standard Dialect SpringStandard Dialect

Where it comes from

org.thymeleaf.standard.StandardDialect in the core thymeleaf artifact

org.thymeleaf.spring6.dialect.SpringStandardDialect in thymeleaf-spring6, extending StandardDialect

Variable expressions

OGNL

Spring Expression Language (SpEL)

Typical host

Standalone programs and plain servlets

Spring MVC and Spring WebFlux

Thymeleaf 3.1.5 still ships OGNL as a dependency of the core artifact, which is why ognl appears in the Maven dependency tree of a Spring Boot application even though the Spring integration evaluates with SpEL. In a Spring application, use SpEL syntax, because that is what runs.

Dialects registered in a Spring Boot application

A Spring Boot application registers every dialect it finds. The scratch project of this page has the Layout Dialect, the htmx dialect of htmx-spring-boot-thymeleaf and Spring Security’s sec: dialect on its classpath, and listing the dialects of the auto-configured SpringTemplateEngine shows each of them next to the SpringStandard Dialect:

Listing the dialects of the auto-configured engine
@Autowired SpringTemplateEngine springEngine;
@Autowired ThymeleafViewResolver viewResolver;

@Test
void springEngineDialects() {
    springEngine.getDialects().forEach(d -> System.out.println(d.getName()));
    System.out.println(viewResolver.getClass().getName());
}

The run printed SpringStandard, Htmx, SpringSecurity and Layout (plus a dialect of the scratch project itself) and the view resolver class org.thymeleaf.spring6.view.ThymeleafViewResolver. The Spring Boot Auto-configuration & Properties page explains how those beans are created.

Writing a dialect

Because the Standard Dialect is itself only a dialect, the engine can host your own. A dialect is registered with TemplateEngine.addDialect; an attribute processor receives the host tag and its attribute value and acts on the element through a handler. The following minimal dialect adds an up:shout attribute that replaces the element body with the upper-cased attribute value. It is a deliberately tiny illustration of the extension model, which the Extending Thymeleaf page covers in depth with the official tutorial’s sample dialects.

A dialect with one attribute processor
class UpperDialect extends AbstractProcessorDialect {
    UpperDialect() { super("Upper dialect", "up", 1000); }

    @Override
    public Set<IProcessor> getProcessors(String prefix) {
        return Set.of(new AbstractAttributeTagProcessor(
                TemplateMode.HTML, prefix, null, false, "shout", true, 1000, true) {
            @Override
            protected void doProcess(ITemplateContext context, IProcessableElementTag tag,
                                     AttributeName attributeName, String attributeValue,
                                     IElementTagStructureHandler structureHandler) {
                structureHandler.setBody(attributeValue.toUpperCase(), false);
            }
        });
    }
}

TemplateEngine engine = new TemplateEngine();
engine.setTemplateResolver(resolver);
engine.addDialect(new UpperDialect());
engine.process("<p up:shout=\"hello\">x</p>", new Context());   // <p>HELLO</p>

The engine then holds two dialects, Standard and Upper dialect; the first is registered by default and several dialects can be active at once.

th:* versus data-th-*, and the xmlns:th Namespace

Every Standard Dialect attribute can be written in two equivalent notations. The namespaced form th:text is the one used throughout this documentation. The HTML5-friendly form data-th-text follows the standard data-{prefix}-{name} convention for custom attributes, so the template is valid HTML5 with no namespaced names. The tutorial adds that the syntax is available to all dialects, not only the Standard one, that custom elements follow the {prefix}-{name} form (th-block is the same as th:block), and that there is no intention to deprecate the namespaced syntax. The th:* notation is the general one and works in every template mode, while the data- notation is allowed only in the HTML mode.

Both notations render the same output
<p th:text="${t}">x</p>
<p data-th-text="${t}">x</p>

With t equal to Dune both lines produce <p>Dune</p>; the run compared the two outputs and they were identical.

The xmlns:th="http://www.thymeleaf.org" declaration that many templates carry on the <html> element has no influence on processing. It only stops an IDE from complaining about unknown th:* attributes. It can be omitted, and the data-th-* notation avoids the need for it, but Spring Boot starters and the official examples keep it for editor support.

From HTML to HTML, via HTML

Why does Thymeleaf offer both a lenient HTML mode and a strict XML mode? The official article From HTML to HTML (via HTML) tells the story. HTML grew out of SGML, browsers were forgiving of malformed markup, XHTML demanded well-formed XML, and HTML5 returned to an error-tolerant syntax with specified error handling and no DTD. Thymeleaf’s HTML mode follows the HTML5 outlook: it accepts HTML5, HTML 4 and XHTML without checking well-formedness, while the XML mode is available for the cases where a strict XML document is needed. In practice, write HTML5 templates (<!DOCTYPE html>) in HTML mode and reserve XML mode for feeds and other XML documents.

The Engine Pipeline and Its Caches

When an application asks the engine to render a template, four things happen in order, drawn in the figure below.

  1. Resolution. One or more template resolvers (org.thymeleaf.templateresolver.ITemplateResolver) turn a template name such as books/list into an actual resource and decide its template mode. Spring Boot configures a SpringResourceTemplateResolver with the prefix classpath:/templates/ and the suffix .html.

  2. Parsing. The parser reads the template with parsers for markup and for text and produces a sequence of events (open tag, text, close tag, comment and so on). The markup parser of the engine is AttoParser, the attoparser artifact on the Thymeleaf dependency list.

  3. Processing. Processors, one for each kind of behaviour, modify the event sequence by combining the original template with the data in the Context. Expressions are evaluated at this stage, with OGNL or SpEL.

  4. Output. The resulting events are written to a Writer.

The Thymeleaf pipeline: template name to template resolvers to the template cache, then parser, processors and writer, with the context and the expression cache

Thymeleaf keeps two caches, managed by a StandardCacheManager by default. The template cache stores the event sequences of parsed templates, because cloning an in-memory sequence is much quicker than reading a file and parsing it again; the tutorial reasons that web applications have only a few dozen small templates that do not change while the application runs. The expression cache stores parsed expressions. The official javadoc gives the defaults: 200 entries for the template cache and 500 for the expression cache. Caching can be switched off per resolver (setCacheable(false)) and individual entries can be evicted:

Resizing and clearing the caches
StandardCacheManager cacheManager = new StandardCacheManager();
cacheManager.setTemplateCacheMaxSize(100);       // default 200
cacheManager.setExpressionCacheMaxSize(300);     // default 500
engine.setCacheManager(cacheManager);

engine.process("intro/search", ctx);             // parses the template, one cache entry
engine.clearTemplateCache();                     // empties the template cache
engine.clearTemplateCacheFor("intro/search");    // or removes a single template

After the first call, the run reported one entry in the template cache and two in the expression cache; after clearTemplateCache() the template cache was empty. Spring Boot exposes the switch as spring.thymeleaf.cache, which defaults to true and is usually set to false only in a development profile, so a template edit shows up without a restart. The Performance & Production and Getting Started pages return to this.

Two further points belong to the processing stage. First, since 3.1 every expression is subject to general class restrictions, and a stricter restricted mode applies in contexts where the input is less trusted, such as preprocessing, unescaped output with th:utext, th:on* event attributes, fragment names and parameters, and the URL base; Appendix D of the tutorial lists them and the Template Security & XSS page covers the details. Nothing on this page uses those contexts. Second, template problems surface as exceptions at render time and not at start-up: a syntax error in a template raises a TemplateInputException ("An error happened during template parsing"), and the parser of the XML mode throws exceptions when the input is not well formed.

Standalone, Spring MVC and Spring WebFlux

The Spring integration lives in org.thymeleaf:thymeleaf-spring6 (no thymeleaf-spring7 exists on Maven Central; Spring Framework 7 applications use the same artifact, which is what the Spring Boot 4.1.1 dependency tree resolves). It supplies:

  • SpringTemplateEngine (org.thymeleaf.spring6), which applies the SpringStandard Dialect automatically and enables Spring’s MessageSource for message resolution;

  • SpringResourceTemplateResolver, which integrates with Spring’s own resource resolution;

  • ThymeleafViewResolver (org.thymeleaf.spring6.view), the Spring MVC ViewResolver that turns the view name a controller returns into a Thymeleaf-rendered response;

  • ThymeleafReactiveViewResolver (org.thymeleaf.spring6.view.reactive), the WebFlux counterpart. It supports three modes: FULL (the whole output is generated in memory and sent at once), CHUNKED (output in chunks no larger than a configured maximum, with backpressure) and DATA-DRIVEN (markup rendered as a reactive data stream arrives).

The tutorial Thymeleaf + Spring covers Spring MVC; it has no WebFlux chapter, so reactive rendering is documented in the thymeleaf-spring6 javadoc and the Spring documentation, and this section treats it on the Reactive Rendering with WebFlux page. Spring Boot selects the right configuration for a servlet or reactive application through ThymeleafAutoConfiguration.

Environment Entry point Where this section covers it

Standalone

TemplateEngine.process(...) with a Context

the standalone-engine page and the e-mail page

Spring MVC

a @Controller method returning a view name, resolved by ThymeleafViewResolver

the Spring MVC pages and every page’s examples

Spring WebFlux

a handler returning a view name, resolved by ThymeleafReactiveViewResolver

the reactive page

Spring Boot in Two Minutes

A Spring Boot web application is the usual host for Thymeleaf, so a few Spring concepts come up on every page. They are documented elsewhere on this site; the table maps each one to the Thymeleaf feature that uses it.

Spring concept What it gives a Thymeleaf application Documented in

Dependency injection and beans

Controllers, services and repositories are beans injected by constructor. Thymeleaf’s beans (the engine, the view resolver, dialects) are beans too, so you add a dialect or a template resolver by declaring a bean.

Core Concepts

Spring MVC

@Controller methods fill a Model and return a view name. The model becomes the template’s variables.

Core Annotations

Spring Data

Repositories supply the entities that templates iterate over.

Spring Data JPA

Spring Security

Authentication, CSRF protection and the sec: dialect.

Spring Security

Starters and dependency management

spring-boot-starter-thymeleaf brings the engine, and the Boot BOM manages the Thymeleaf, Layout Dialect and Spring Security dialect versions, so a Boot project does not hard-code them. htmx-spring-boot is not managed by the BOM and needs an explicit version.

Getting Started

Configuration

application.properties with the spring.thymeleaf.* keys and server.port.

Configuration & Profiles

A project is created with Spring Initializr and started with ./mvnw spring-boot:run; the next page of this section, Getting Started, walks through the generated layout (src/main/resources/templates and static) and the first controller. Here is the smallest useful slice of the Bookshelf, a controller and a template that the scratch project compiled and tested:

A controller and a natural list template
@Controller
@RequestMapping("/books")
public class BookController {
    private final BookRepository repo;

    public BookController(BookRepository repo) { this.repo = repo; }

    @GetMapping
    public String list(Model model) {
        model.addAttribute("books", repo.findAll());
        return "books/list";           // resolved to classpath:/templates/books/list.html
    }
}
<ul>
  <li th:each="b : ${books}" th:text="${b.title}">Dune</li>
</ul>

Server-Side Rendering, SPAs and Server-Driven UI

Thymeleaf is one way of building the user interface of a web application, and it helps to place it among the others.

Approach Where the HTML is produced Typical trade-off

Server-side rendering (Thymeleaf, JSP, FreeMarker)

On the server, per request, from templates and a model.

Simple deployment, good first load and search-engine friendliness, one language on the server; every interaction is a round trip unless it is enhanced.

Single-page application (React, Vue, Angular)

In the browser, from JSON fetched from an API.

Rich client behaviour, a separate front-end build and a JSON API to secure and version.

Server-driven UI (Vaadin)

The UI is written in Java on the server and the framework synchronises the browser.

No hand-written front end for typical business screens; server session state per user.

Hypermedia-driven (Thymeleaf plus htmx)

On the server, but in fragments that htmx swaps into the page.

Server rendering with partial-page updates and little JavaScript.

The site has pages for the neighbouring choices. The Vaadin architecture page describes the server-driven alternative, and Server-Side Web UI Frameworks compares Thymeleaf, Vaadin and JSF inside a Spring Boot application. The Java server-side page technologies that predate Thymeleaf have sections of their own: JSF in the JSF (JavaServer Faces) section, and JSP and Struts in the JSP & Struts section. GWT, which compiles Java to JavaScript, has its own GWT (Google Web Toolkit) section.

Where htmx fits

htmx is a small JavaScript library that lets any element issue an HTTP request and swap the HTML that comes back into the page, all declared with hx-* attributes. The htmx essay Hypermedia-Driven Applications defines the style: interactions are declared in HTML attributes instead of imperative scripts, and the server exchanges HTML (hypermedia) with the client instead of JSON. That fits Thymeleaf well, because a Thymeleaf template can render either a whole page or only a fragment of it from the same file.

The example below is one endpoint that returns the full page for a normal request and only the results fragment when htmx sends its HX-Request header. The hx-get button asks for the fragment and hx-target/hx-swap replace the list:

One endpoint, a page or a fragment
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>Search</title>
  <script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.11/dist/htmx.min.js"></script>
</head>
<body>
  <button hx-get="/search" hx-target="#results" hx-swap="outerHTML">Refresh</button>
  <ul id="results" th:fragment="results">
    <li th:each="b : ${books}" th:text="${b.title}">Prototype title</li>
  </ul>
</body>
</html>
@GetMapping("/search")
public String search(Model model,
                     @RequestHeader(name = "HX-Request", required = false) String hxRequest) {
    model.addAttribute("books", repo.findAll());
    // both view names are constants, never built from request input
    return hxRequest != null ? "intro/search :: results" : "intro/search";
}

A request without the header produced the complete document starting with <!DOCTYPE html>; a request with HX-Request: true produced only the <ul id="results"> element. In a real application the htmx-spring-boot library gives @HxRequest and related annotations that make this negotiation declarative.

The books of the same author add two companions that the htmx-era Spring stack keeps recommending: Alpine.js for the small pieces of client state that do not belong on the server, following the htmx project’s "Locality of Behavior" principle, and Tailwind CSS for styling. The same essay lists hyperscript, Alpine.js and inline vanilla JavaScript as ways of keeping such scripting next to the markup it affects. This site documents Tailwind in its own Tailwind CSS section; the Styling & Front-end Assets page covers assets, WebJars and live reload, and the Tooling & IDE Support page the editor plugins.

Thymeleaf Compared with Other Template Engines

The table compares Thymeleaf with engines on the JVM and with the engines of the other stacks documented on this site. Each row was checked against the engine’s own documentation on 2026-10-07; the "Compared with Thymeleaf" column is a summary of the difference, not a ranking.

Engine What its documentation says Compared with Thymeleaf

Jakarta Pages (JSP) with JSTL

A template engine for web applications that mixes text with custom tags, expression language and embedded Java; the result is compiled into a Jakarta Servlet. Jakarta Pages 4.0 belongs to Jakarta EE 11.

Not natural: a JSP cannot be previewed without a server. Spring Boot’s reference says JSPs should be avoided when possible because of known limitations with embedded servlet containers, and Thymeleaf is the usual replacement. See JSP, EL & JSTL.

Apache FreeMarker

A Java library to generate text output (HTML pages, e-mails, configuration files, source code), not limited to web use.

Directive-based syntax that is not natural HTML; strong in text generation. Auto-configured by Spring Boot.

Mustache

"Logic-less templates", with a specification and implementations such as Mustache.java and JMustache.

Deliberately minimal and portable across languages; no expression language or dialects. Auto-configured by Spring Boot.

Groovy templates (MarkupTemplateEngine)

An optimised engine for XML-like markup and other text; templates are Groovy code in a builder DSL that compiles to bytecode, with optional type checking, layouts and includes. Auto-escaping is off by default.

Templates are code rather than markup; very flexible, not natural. Auto-configured by Spring Boot.

JTE

"A secure and lightweight template engine for Java and Kotlin" with typesafe templates, compile-time escaping, precompiling and hot reload, and integrations for Spring Boot, Spring Web MVC, Quarkus, Javalin and others.

Expressions are plain Java checked by the compiler, where Thymeleaf expressions are checked at render time. Not among the engines Spring Boot auto-configures.

Pebble

A Java templating engine inspired by Twig, with a syntax similar to Python’s Jinja, template inheritance, extensible tags and filters, autoescaping and internationalisation.

A familiar Twig and Jinja style with inheritance built in; not natural HTML.

Qute

The Quarkus templating engine, with build-time validation of typed templates, reduced reflection for native images and immediate template reload in dev mode.

Tied to Quarkus. See Qute Templating on this site.

Django templates

The Django template language: variables, tags, filters and comments, with a Jinja2 backend as an alternative.

Tag-based rather than attribute-based; see Django Templates.

Jinja2

"A fast, expressive, extensible templating engine" with Python-like placeholders.

Very close in spirit to Pebble. See the Jinja2 section of Static Files, Templates & Frontend.

Laravel Blade

All Blade templates are compiled into plain PHP code and cached until they are modified.

Directives and components rather than attributes; see Blade Templates.

ASP.NET Core Razor

A markup syntax for embedding .NET code into webpages, with the @ symbol switching from HTML to C#.

C# in the template; see Razor Syntax and Tag Helpers.

askama (Rust)

A Rust template engine modelled on Jinja: a derive macro generates Rust code from the template at compile time and the types you pass in are checked by the compiler.

Compile-time checked, where Thymeleaf is interpreted at run time; see Server-side Templates and htmx in the Axum section.

Two observations explain most of the table. First, Spring Boot’s reference documents FreeMarker, Groovy, Thymeleaf and Mustache as the template engines with auto-configuration support; everything else needs its own integration. Second, the engines split into those that keep the template valid, previewable HTML (Thymeleaf is the clearest example) and those that trade that property for a more compact or more type-checked syntax (JTE, Qute, askama, Razor).

The same ideas appear elsewhere on this site: the Django partials and htmx page and the Axum htmx page show the htmx pattern from the previous section in those stacks.

Version History and Versioning Policy

Thymeleaf follows x.y.z versioning, as its FAQ describes: a major version marks changes to the core or architecture, or many API changes that may break compatibility; a minor version adds features with few API changes and gives deprecation periods; a build version contains bug fixes and performance or documentation improvements and is a drop-in replacement within the same major and minor version.

Line First release on Maven Central What it brought

2.1

2.1.0.RELEASE, November 2013 (last: 2.1.6.RELEASE, November 2017)

The last line built on an XML-based engine, according to the 3.0 migration article.

3.0

3.0.0.RELEASE, May 2016 (last: 3.0.15.RELEASE, January 2022)

A new engine with a significant performance improvement, and, per the migration article, the textual template modes (TEXT, JAVASCRIPT, CSS), an improved inlining mechanism, fragment expressions (~{...}), the no-operation token and decoupled template logic.

3.1

3.1.0.RELEASE, November 2022 (latest: 3.1.5.RELEASE, April 2026)

Jakarta namespace support, IWebApplication and IWebExchange replacing the servlet types in the core API, the removal of #request, #response, #session and #servletContext, and tighter expression restrictions.

The dates are the publication dates of the artifacts on Maven Central. The archived article Thymeleaf 3.1: What’s new (archived, because the live URL returns 404) lists the changes of 3.1: jakarta.* support, th:include deprecated in favour of th:insert, the unwrapped fragment syntax deprecated in favour of ~{...}, th:substituteby removed, and the ${execInfo} variable removed. The archived Thymeleaf 3.0 migration guide (archived) covers the move from 2.1.

On the project’s GitHub page, three milestones were open on 2026-10-07 with no due dates: 4.0 (14 open issues), 3.2.0 (5 open issues) and 3.1.6 (none open). Plan for 3.1.x today and treat 4.0 as not yet scheduled.

What Changed Recently

  • Security advisories. Three advisories are published on the project’s GitHub page: CVE-2026-41901 (syntax patterns of the sandboxed expression restrictions), CVE-2026-40478 and CVE-2026-40477 (scope of accessible objects). This section teaches 3.1.5 as the minimum version, and a Spring Boot 4.1.1 project already resolves it.

  • Spring Boot 4. The auto-configuration moved to its own module, spring-boot-thymeleaf, with the package org.springframework.boot.thymeleaf.autoconfigure, and there is a test starter, spring-boot-starter-thymeleaf-test. The spring.thymeleaf.enabled property has been deprecated at error level since 4.0.0 ("no longer required"), so it no longer switches the auto-configuration off.

  • Layout Dialect 4.0. Version 4.0.1 (2026-03-25) is built for Java 17; layout:collect was removed and its configuration constructors are deprecated.

  • htmx and htmx-spring-boot. htmx 2.0.11 is the latest npm tag and 4.0.0 is published as next on 2026-10-07; htmx-spring-boot 5.2.0 is the current release and the project’s main branch already documents 6.0.0 for htmx 4.

  • Spring Framework 6.2 and later. FragmentsRendering renders several fragments in one response, which is the natural fit for htmx out-of-band swaps.

Where the Books Are Outdated

This section consulted two books by Wim Deblauwe: Taming Thymeleaf and Modern frontends with htmx; the section bibliography lists them. The introduction chapters of both give a sound picture of the concepts above (the server-side request flow, natural templates, the Spring building blocks, and the htmx plus Thymeleaf pairing), but the platform they describe has moved on:

Area The books (as consulted) Current at 2026-10-07

Platform of Taming Thymeleaf (PDF v1.1.1, 2021)

Java 11, Spring Boot 2.3, Thymeleaf 3.0, javax.*

Java 21 or later, Spring Boot 4.1, Thymeleaf 3.1.5, jakarta.*

Platform of Modern frontends with htmx (v1.0.0, 2023)

Spring Boot 3.1.4, htmx 1.9, htmx-spring-boot 3.x

Spring Boot 4.1, htmx 2.0.x (4.0 in preview), htmx-spring-boot 5.x

Spring Security dialect

thymeleaf-extras-springsecurity5

thymeleaf-extras-springsecurity6, registered by Spring Boot

Leanpub now sells an edition of Taming Thymeleaf updated for Spring Boot 3, so the platform row refers to the PDF that was consulted. Later pages note the outdated details next to the concepts they touch, for example the removed expression objects, the Layout Dialect 4.0 migration and the htmx 2 changes. The What’s Changed & Migration page collects them.