What’s Changed & Migration

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 a dated migration map, checked on 2026-10-08 against Thymeleaf 3.1.5.RELEASE (2026-04-21), Spring Boot 4.1.1 (Spring Framework 7.0.9, Spring Security 7), Java 21, Layout Dialect 4.0.1, htmx 2.0.11 and htmx-spring-boot 5.2.0. Every before/after example that can be run was compiled and executed on that stack in a scratch Spring Boot 4.1.1 application (Spring MVC, MockMvc, and the standalone engine); the output and the compiler or engine messages shown are what that run produced. The Thymeleaf parts apply to standalone use, to Spring MVC and to Spring WebFlux alike unless a row says otherwise; the Spring Boot, Spring Security and htmx-spring-boot parts are Spring MVC specific. Use it in three ways: read the timeline and the per-library sections when you are upgrading an existing application, read the table at the end ("Where the reference books are outdated") when you are learning from the two books this section consulted, and keep the upgrade checklist next to your build file.

The version map

The timeline puts the releases this page talks about on one axis; the table below it is the baseline the rest of this section teaches. Dates are the Maven Central, npm or GitHub release dates of the first release of each line, except where the text says otherwise.

timeline title Thymeleaf and its companions 2013 : Thymeleaf 2.1.0 in November 2016 : Thymeleaf 3.0.0 in May : Layout Dialect 2.0.0 in July 2018 : Spring Boot 2.0 in March with Thymeleaf 3.0 2020 : htmx 1.0.0 in November 2021 : Layout Dialect 3.0.0 in August 2022 : Thymeleaf 3.1.0 and Spring Boot 3.0 in November : jakarta namespace and Java 17 2023 : Thymeleaf 3.1.2 in July : htmx-spring-boot 3.0.0 in August 2024 : htmx 2.0.0 in June : Spring Boot 3.4 in November : htmx-spring-boot 4.0.0 in November : Thymeleaf 3.1.3 in December 2025 : Spring Boot 4.0 in November : htmx-spring-boot 5.0.0 in November 2026 : Layout Dialect 4.0.0 in February : Thymeleaf 3.1.4 and 3.1.5 in April : Spring Boot 4.1 in June : htmx 4.0.0 on the npm next tag in August : htmx-spring-boot 5.2.0 in October
Table 1. Baseline of this section, verified on 2026-10-08
Component Version Notes

Thymeleaf core, thymeleaf-spring6, thymeleaf-extras-springsecurity6

3.1.5.RELEASE (2026-04-21)

3.1.5 is the minimum version this section teaches. There is no thymeleaf-spring7: Spring Framework 7 uses thymeleaf-spring6. Core needs Java 8, the spring6 and springsecurity6 modules need Java 17.

thymeleaf-testing

3.0.4.RELEASE is the last release; 3.1.0.M2 is the only 3.1 milestone

There is no 3.1 final; see the Testing page.

Spring Boot

4.1.1 (Spring Framework 7.0.9)

Manages Thymeleaf 3.1.5, Layout Dialect 4.0.1 and thymeleaf-extras-data-attribute 2.0.1. Boot 3.5.16 also manages Thymeleaf 3.1.5 but its open-source support ended on 2026-06-30.

Thymeleaf Layout Dialect

4.0.1 (2026-03-25)

Built for Java 17 and Groovy 5.

htmx

2.0.11 is npm latest; 4.0.0 is published under next

This section teaches htmx 2.

htmx-spring-boot and htmx-spring-boot-thymeleaf

5.2.0 (2026-10-02)

Not managed by the Boot BOM. 6.0.0, for htmx 4 and Boot 4.1, is documented on the main branch but was not on Maven Central on this date.

Thymeleaf 2.1 to 3.0 (2016)

Thymeleaf 3.0 was a rewrite of the engine, not an update of it. Most 2.1 templates keep working, as the archived ten-minute migration guide says, so the work is mostly in configuration and in custom dialects. That guide now returns 404 on thymeleaf.org; the copy cited here is the Wayback Machine capture (archived 2025-01-18).

Table 2. What changed between 2.1 and 3.0
Area Change in 3.0 What you do

Parsing and engine

The engine was rewritten around an event-based processing model, and HTML is no longer parsed as XML, so unclosed tags and unquoted attributes in HTML mode are tolerated. Rendering is much faster and uses less memory.

Nothing for templates. Re-test pages that relied on the old XML-based parser.

Template modes

Modes are the TemplateMode enum now, not strings, and the set is HTML, XML, TEXT, JAVASCRIPT, CSS and RAW (two markup modes, three textual modes, and a no-operation mode). The 2.1 modes HTML5, XHTML, LEGACYHTML5 and the validating modes are gone.

Set TemplateMode.HTML or TemplateMode.XML on resolvers. See Textual Template Modes.

Inlining

[[...]] and [(...)] work in HTML and XML modes without an enclosing th:inline="text", which is no longer needed.

Remove th:inline="text" for a small speed gain. See Inlining.

Textual modes

New syntax for TEXT, JAVASCRIPT and CSS templates: element-like [# th:each=...]...[/] blocks and natural JavaScript and CSS templates.

Use it for plain-text emails, generated scripts and stylesheets.

Fragment expressions

A new expression type that returns markup, written with the tilde and braces envelope, can be passed as a fragment parameter.

Use th:insert and th:replace (th:substituteby is deprecated in favour of th:replace), and pass markup between fragments. See Fragments.

No-operation token

The underscore token means "do nothing", so the prototype text of the tag becomes the default value of an expression.

Use it as the right side of an elvis or conditional expression.

Decoupled template logic

An optional .th.xml file next to an HTML or XML template can inject the Thymeleaf attributes by markup selector, leaving the HTML free of Thymeleaf.

Enable it on the resolver. See Natural Templates & Decoupled Logic.

Dialect API

A new, smaller IDialect with sub-interfaces, processors for every kind of event, pre- and post-processors, dialect precedence and expression-object dialects. Dialects written for 2.1 must be rewritten.

Check that every third-party dialect has a 3.x release before upgrading. See Extending Thymeleaf.

Servlet independence and reactive

The engine can run without the Servlet API and can produce output in chunks under back-pressure.

Prepared the ground for WebFlux support; see Reactive Rendering with WebFlux.

Spring integration

SpringResourceTemplateResolver is the recommended resolver for Spring applications, and the SpEL compiler can be switched on.

Use SpringResourceTemplateResolver and setEnableSpringELCompiler(true).

The next block runs the three most visible template changes on 3.1.5: inlining without th:inline, the no-operation token, and the TEXT mode block syntax.

Inlining, the no-operation token and the TEXT mode on 3.1.5
Context ctx = new Context();
ctx.setVariable("user", new java.util.HashMap<String, Object>());
ctx.setVariable("title", "Emma");
ctx.setVariable("items", List.of("Emma", "Persuasion"));

// HTML: no th:inline="text" needed
render(TemplateMode.HTML, "<p>Now reading [[${title}]]</p>", ctx);
// no-op token: the prototype text becomes the default
render(TemplateMode.HTML,
    "<span th:text=\"${user['name']} ?: _\">no reader signed in</span>", ctx);
// TEXT mode
render(TemplateMode.TEXT, "[# th:each=\"item : ${items}\"]\n- [(${item})]\n[/]", ctx);
Output
<p>Now reading Emma</p>
<span>no reader signed in</span>

- Emma
- Persuasion

Fragment expressions and decoupled logic are the two features of 3.0 that change how pages are written. The first example passes the title and link elements of a page into a shared head fragment as markup; the second keeps the page free of Thymeleaf and puts the logic in a .th.xml file.

Passing markup to a fragment (Thymeleaf 3.0 and later)
<!-- mig30/shell.html -->
<head th:fragment="shell(title, links)">
  <title th:replace="${title}">Bookshelf</title>
  <link rel="stylesheet" href="/css/site.css">
  <th:block th:replace="${links}" />
</head>

<!-- mig30/page.html -->
<head th:replace="~{mig30/shell :: shell(~{::title}, ~{::link})}">
  <title>Shelf</title>
  <link rel="stylesheet" href="/css/shelf.css">
</head>
Rendered head
<head>
  <title>Shelf</title>
  <link rel="stylesheet" href="/css/site.css">
  <link rel="stylesheet" href="/css/shelf.css">
</head>
Decoupled logic: a plain HTML table and its shelf.th.xml
<?xml version="1.0"?>
<thlogic>
  <attr sel="#shelfTable" th:remove="all-but-first">
    <attr sel="/tr[0]" th:each="book : ${books}">
      <attr sel="td.title" th:text="${book.title}" />
      <attr sel="td.author" th:text="${book.author}" />
    </attr>
  </attr>
</thlogic>
Resolver switch and rendered rows (two books in the model)
ClassLoaderTemplateResolver r = new ClassLoaderTemplateResolver();
r.setTemplateMode(TemplateMode.HTML);
r.setSuffix(".html");
r.setUseDecoupledLogic(true);   // look for shelf.th.xml beside shelf.html
<tr>
  <td class="title">Emma</td>
  <td class="author">Jane Austen</td>
</tr>
<tr>
  <td class="title">Persuasion</td>
  <td class="author">Jane Austen</td>
</tr>

Thymeleaf 3.0 to 3.1 (2022)

Thymeleaf 3.1.0.RELEASE was published on 2022-11-17, a week before Spring Boot 3.0, and it is the release that touches templates most since 3.0. Boot 3.0 pulled it in automatically, so many applications met the changes below as exceptions after a Boot upgrade rather than as a Thymeleaf upgrade. The official "3.1: What’s new and how to migrate" article is gone from thymeleaf.org (404); the content below is checked against its Wayback Machine capture (archived 2025-01-01) and against Appendix D of the tutorial, which now holds the restrictions content.

Table 3. What changed between 3.0 and 3.1
Area Change in 3.1 What you do

Jakarta namespace

Support for the Servlet API 5.0 jakarta.* namespace, without dropping javax.* for older Servlet versions.

Use Spring Boot 3 or later with jakarta.* imports.

Spring and Spring Security modules

New thymeleaf-spring6 and thymeleaf-extras-springsecurity6; support for Spring older than 5.0 and for Spring Security older than 5.0 was removed.

Replace thymeleaf-spring5 with thymeleaf-spring6 and thymeleaf-extras-springsecurity5 with thymeleaf-extras-springsecurity6. A Boot application only changes the Boot version.

Java baseline

Java 8 is the minimum for core; Java 17 for the spring6 and springsecurity6 modules, because Spring 6 needs it.

Plan the JDK upgrade with the Boot upgrade.

#temporals in core

The thymeleaf-extras-java8time module was merged into core, so #temporals is always available, and can format in non-default locales. The extras module stays frozen at 3.0.4.

Remove thymeleaf-extras-java8time from the build.

Web API abstraction

IWebApplication, IWebExchange, IWebRequest and IWebSession hide whether the host uses javax.* or jakarta.*. Spring applications never see them.

Only standalone servlet code changes; see the example below.

Removed expression objects

#request, #response, #session and #servletContext are no longer available in expressions, and neither is the deprecated ${execInfo} variable.

Expose what the template needs as model attributes, or use the param, session and application variables, and write ${#execInfo}.

th:include deprecated

Still works in 3.1.5, but it will be removed, and th:insert has slightly different semantics.

Use th:insert, with th:remove="tag" or a th:block fragment where only the contents are wanted.

Unwrapped fragment expressions deprecated

th:insert="common :: header" still works, but th:insert="~{common :: header}" is the form to write.

Wrap every fragment expression.

th:substituteby removed

The attribute, deprecated since 3.0, is gone.

Use th:replace.

Restrictions

A general ban on calling methods, constructors and static references of classes in java.*, javax.*, jakarta.*, jdk.*, com.sun.*, sun.* and a few other packages, with an allow-list for the common value types.

Move the logic to a bean or a model attribute. See Template Security & XSS.

Smaller changes

th:each iterates a java.util.stream.Stream directly, and SpringTemplateEngine accepts a custom message resolver. The test infrastructure moved to JUnit 5, and the source repositories were merged into one.

Nothing to change.

Fragment attributes: th:include, unwrapped expressions and th:substituteby

The three deprecations are the ones that appear in older books and tutorials. The fragment used by every example below is:

<!-- mig/frag.html -->
<p th:fragment="frag" class="content">something</p>
<th:block th:fragment="block">something</th:block>
Before: deprecated or removed forms
<div id="main" th:include="mig/frag :: frag">placeholder</div>
<p id="main" th:substituteby="~{mig/frag :: frag}">placeholder</p>
After: th:insert and th:replace
<div id="main" th:insert="~{mig/frag :: frag}">placeholder</div>
<div id="main" th:insert="~{mig/frag :: block}">placeholder</div>
<p id="main" th:replace="~{mig/frag :: frag}">placeholder</p>
Table 4. Output of each form on 3.1.5
Template Rendered

th:include="mig/frag :: frag" (deprecated; unwrapped as well)

<div id="main">something</div>, only the content of the fragment.

th:insert="~{mig/frag :: frag}"

<div id="main"><p class="content">something</p></div>: the fragment tag is inserted too.

th:insert="~{mig/frag :: block}" (fragment on a th:block)

<div id="main">something</div>: same result as the old th:include.

th:replace="~{mig/frag :: frag}"

<p class="content">something</p>: the host tag is replaced.

th:substituteby="~{mig/frag :: frag}" (removed)

The host tag keeps its placeholder text and gains an unknown substituteby attribute whose value is the escaped markup of the fragment. No error, and the fragment is not included.

The last row is the dangerous one: nothing fails, the page just stops including the fragment. Search the templates for the removed attribute before upgrading. The tutorial’s Appendix D still lists th:substituteby among attributes in its restricted-mode text; that is stale wording, not evidence that the attribute exists.

Removed expression objects

On Spring MVC, the 3.1 engine refuses #request outright and says how to bring it back:

Before: web API objects in an expression
<p th:text="${#request.contextPath}">/ctx</p>
Engine message on 3.1.5
java.lang.IllegalArgumentException: The 'request','session','servletContext' and
'response' expression utility objects are no longer available by default for
template expressions and their use is not recommended. In cases where they are
really needed, they should be manually added as context variables.

The replacement is to hand the template exactly what it needs. A @ModelAttribute method (in the controller or in a @ControllerAdvice) exposes request data, and the param and session variables read request parameters and session attributes without any web object:

After: model attribute plus the param and session variables
@ModelAttribute("contextPath")
String contextPath(HttpServletRequest request) {
    return request.getContextPath();
}
<p id="ctx" th:text="${contextPath}">/ctx</p>
<p id="q" th:text="${param.q}">q</p>
<p id="s" th:text="${session.theme}">theme</p>
Rendered for GET /mig/request-new?q=emma with theme set to dark in the session
<p id="ctx"></p>
<p id="q">emma</p>
<p id="s">dark</p>

(The context path is empty because the application is deployed at the root.) For a link, @{/path} already prepends the context path, so most templates never needed #request.contextPath at all.

The ${execInfo} variable fails differently, because the unknown variable is null:

<p th:text="${execInfo.templateName}">name</p>   <!-- 3.1: EL1007E, property cannot be found on null -->
<p th:text="${#execInfo.templateName}">name</p>  <!-- renders mig/execinfo-new -->

General class restrictions

Calling methods or constructors of core JDK classes is refused unless the class is on the allow-list; this is the change most likely to break a template that used T(...) or a JDK object directly.

Before: a JDK class used in an expression
<p th:text="${T(java.lang.Runtime).getRuntime().availableProcessors()}">n</p>
Engine message on 3.1.5
EvaluationException: Access is forbidden for type 'java.lang.Runtime' in this expression context.

The fix is the same for every such case: compute the value in Java and put it in the model. The allow-list covers the basic java.lang and java.math value types, the main collection types, java.util.Optional, java.util.stream.Stream, java.util.Locale, java.util.Date and java.util.Calendar, and later patch releases widened it (see the patch line below). The rules, the full list and the restricted-mode contexts are on Template Security & XSS and in Appendix D.

#temporals and Stream in core

Two things that needed extra modules or conversions are plain core features now. The scratch application has no thymeleaf-extras-java8time dependency and still renders:

<p th:text="${#temporals.format(day, 'yyyy-MM-dd')}">date</p>
<ul><li th:each="n : ${numbers}" th:text="${n}">n</li></ul>
Output with a LocalDate and a Stream<Integer> in the model
<p>2026-10-08</p>
<ul><li>1</li><li>2</li><li>3</li></ul>

From javax to jakarta and the web API abstraction

On Spring the change is a dependency change: thymeleaf-spring5 becomes thymeleaf-spring6 and thymeleaf-extras-springsecurity5 becomes thymeleaf-extras-springsecurity6. In Java sources, javax.servlet.* becomes jakarta.servlet.*. Code that still imports the old package stops compiling on Spring Boot 4:

Before: javax import
import javax.servlet.http.HttpServletRequest;
Compiler message on the Boot 4.1.1 classpath
A.java:1: error: package javax.servlet.http does not exist

Standalone servlet code that builds the web context itself uses the 3.1 abstraction instead of the Servlet classes:

After: build an IWebExchange from a jakarta request
JakartaServletWebApplication application =
    JakartaServletWebApplication.buildApplication(servletContext);
IWebExchange exchange = application.buildExchange(request, response);
WebContext context = new WebContext(exchange);
engine.process("<p>Searching for <b th:text=\"${param.q}\">term</b></p>", context, out);
Output for GET /shelf?q=emma
<p>Searching for <b>emma</b></p>

The 3.1.1 to 3.1.5 patch line

The 3.1 line has had five patch releases since 3.1.0. The GitHub release notes only link to milestones, so the table lists the milestone issues; the three CVEs come from the GitHub security advisories. Most fixes are about the new restrictions: they relaxed them where valid templates were rejected, and tightened them where the sandbox leaked.

Table 5. The 3.1 patch releases
Release Date Notable changes

3.1.0.RELEASE

2022-11-17

Jakarta, thymeleaf-spring6, restrictions, removals. Milestones M1 to M3 and RC1 to RC2 preceded it.

3.1.1.RELEASE

2022-12-06

Method calls on proxied instances allowed again while still forbidding the creation of proxies; the 3.1.0 artifact no longer referred to milestone Spring dependencies; fixed an IllegalArgumentException when moving from 3.1.0.M3 to 3.1.0.RC1 with forward-headers-strategy: framework.

3.1.2.RELEASE

2023-07-30

AttoParser 2.0.7; static access to org.springframework.util forbidden in expressions; UUID.equals, the OptionalInt, OptionalLong and OptionalDouble members, java.sql.* types and LinkedHashMap iterators no longer wrongly rejected.

3.1.3.RELEASE

2024-12-09

Calling methods of Class<?> restricted; java.util.concurrent.atomic classes, java.util.Currency and Deque.descendingIterator allowed.

3.1.4.RELEASE

2026-04-12

Security release (CVE-2026-40477, CVE-2026-40478). Also: a way to declare classes that are always allowed in SpEL, a public TemplateData constructor for unit-testing extensions, fragment parameters with literal parentheses parse, the Jackson JDK8 module in the JavaScript serializer, and the servlet-context mismatch error fixed.

3.1.5.RELEASE

2026-04-21

Security release (CVE-2026-41901). Also: Java records and Jackson 3 in JavaScript inlining, template recursion no longer ends in a StackOverflowError, stale class loaders no longer cached in SpEL type resolution, the conversion service no longer called for every value of a select element, SpringTemplateEngine no longer overwrites message resolvers set on it, and the restricted-mode check applied to data-th-minlength and data-th-maxlength alike.

Open GitHub milestones (no due dates on 2026-10-08) are 3.1.6, 3.2.0 and 4.0; the thymeleaf.org site announces none of them, and its release-notes page stops at 3.0.15.

Table 6. Security releases of the 3.1 line
CVE Advisory Affected Fixed in Class of problem

CVE-2026-40477

GHSA-r4v4-5mwr-2fwr

3.1.3 and earlier

3.1.4

The expression sandbox did not properly restrict the scope of accessible objects, so sensitive objects were reachable from a template.

CVE-2026-40478

GHSA-xjw8-8c5c-9r79

3.1.3 and earlier

3.1.4

The sandbox did not neutralise certain syntax patterns, so unauthorised expressions could run.

CVE-2026-41901

GHSA-c9ph-gxww-7744

3.1.4 and earlier

3.1.5

The restricted contexts of Appendix D did not recognise some constructs, so unsanitised values used there could run as expressions.

All three are rated critical (CVSS 9.0) and lead to server-side template injection when an application passes unvalidated input to the template engine. The advisories name no workaround other than upgrading and not letting user input reach the engine, which is why 3.1.5 is the minimum this section teaches. Staying on a Boot line whose BOM still manages an older Thymeleaf is the usual way to miss the fix: check the resolved version with mvn dependency:tree and override thymeleaf.version only if the BOM lags. The mechanics, and why view names and fragment selectors must never come from request input, are on Template Security & XSS.

Spring Boot 2 to 3 to 4 for Thymeleaf applications

A Boot upgrade is also a Thymeleaf upgrade, because the BOM chooses the versions. The table shows what each Boot generation managed on its first GA release, and the latest patch where that matters.

Table 7. What the Boot BOM manages
Spring Boot Released Spring Framework Thymeleaf Layout Dialect

2.0.0

2018-03-01

5.0.4

3.0.9

2.3.0

2.7.18 (last 2.7)

2023-11-23

5.3.31

3.0.15

3.0.0

3.0.0

2022-11-24

6.0.2

3.1.0

3.1.0

3.4.0

2024-11-21

6.2.0

3.1.2

3.3.0

3.5.0 (3.5.16 is the latest)

2025-05-22

6.2.7

3.1.3 (3.1.5)

3.4.0

4.0.0 (4.0.8 is the latest)

2025-11-20

7.0.1

3.1.3 (3.1.5)

3.4.0

4.1.0 (4.1.1 is the latest)

2026-06-10

7.0.9

3.1.5

4.0.1

Table 8. What a Thymeleaf application meets at each step
Step Changes that touch Thymeleaf applications

Boot 2 to Boot 3

Java 17 minimum; Jakarta EE 10, so javax.* imports become jakarta.*; Spring Framework 6; Thymeleaf 3.1 with the expression-object removals, the th:include deprecation and the class restrictions above; Spring Security 6, where WebSecurityConfigurerAdapter is gone; the Boot auto-configuration registers the Spring Security dialect when thymeleaf-extras-springsecurity6 is on the classpath.

Boot 3.x

Since 3.4 the Boot @MockBean and @SpyBean test annotations are deprecated in favour of @MockitoBean and @MockitoSpyBean; MockMvcTester (AssertJ) is available.

Boot 3 to Boot 4

Spring Framework 7 and Spring Security 7; auto-configuration is split into modules, and Thymeleaf’s moves to a spring-boot-thymeleaf module (package org.springframework.boot.thymeleaf.autoconfigure); spring-boot-starter-thymeleaf-test is the test starter that goes with it; spring-boot-starter-web is renamed spring-boot-starter-webmvc and has its own spring-boot-starter-webmvc-test; test annotation packages move (for example AutoConfigureMockMvc is in org.springframework.boot.webmvc.test.autoconfigure); @MockBean and @SpyBean are removed; Jackson 3 is the preferred JSON library; DevTools LiveReload is disabled by default; the spring.thymeleaf.enabled property is deprecated.

Boot 4.0 to 4.1

The Layout Dialect 4 line arrives with Boot 4.1, and the DevTools LiveReload feature is deprecated with no replacement.

The Thymeleaf-specific properties and auto-configuration classes are on Spring Boot Auto-configuration & Properties. The pieces below show the code that changes most often when an application written against Boot 2 meets Boot 4.

Dependencies

After (Boot 4.1.1): the starters of the scratch application, with no Thymeleaf versions
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity6</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webmvc-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf-test</artifactId>
    <scope>test</scope>
</dependency>

The Boot 2 equivalent used spring-boot-starter-web and spring-boot-starter-test, and thymeleaf-extras-springsecurity5. thymeleaf-extras-springsecurity6 is listed explicitly because the generated project adds it when Security and Thymeleaf are both chosen; the version comes from the BOM.

Security configuration

Spring Security 6 removed WebSecurityConfigurerAdapter, and the antMatchers method no longer exists on the authorizeHttpRequests registry of Spring Security 7 (Boot 4); requestMatchers replaces it. The old base class no longer compiles:

Before: adapter subclass
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
class B extends WebSecurityConfigurerAdapter { }
Compiler messages on the Boot 4.1.1 classpath
B.java:1: error: cannot find symbol
  symbol:   class WebSecurityConfigurerAdapter
D.java:2: error: cannot find symbol
  symbol:   method antMatchers(String)
  location: variable a of type AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry

The current form is a SecurityFilterChain bean with the lambda DSL and requestMatchers; the scratch application’s class compiled and ran under Boot 4.1.1:

After: SecurityFilterChain bean
@Configuration
public class SecurityConfig {
    @Bean
    SecurityFilterChain chain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(a -> a
                .requestMatchers("/login", "/css/**").permitAll()
                .anyRequest().authenticated())
            .formLogin(Customizer.withDefaults());
        return http.build();
    }
}

@EnableGlobalMethodSecurity still compiles under Boot 4.1.1 but is reported as deprecated; @EnableMethodSecurity supersedes it. The sec: attributes and the thymeleaf-extras-springsecurity6 dialect keep working as before: see Spring Security Integration.

Tests

@MockBean is gone in Boot 4, and its import package no longer exists:

Before
import org.springframework.boot.test.mock.mockito.MockBean;
class C { @MockBean Object service; }
Compiler message on the Boot 4.1.1 classpath
C.java:1: error: package org.springframework.boot.test.mock.mockito does not exist
After: @MockitoBean from Spring Framework
import org.springframework.test.context.bean.override.mockito.MockitoBean;

@SpringBootTest
@AutoConfigureMockMvc
class ShelfHtmxTest {
    @MockitoBean Runnable collaborator;
}

DevTools LiveReload

Boot 4.0 disabled the embedded LiveReload server by default (spring.devtools.livereload.enabled turns it back on), and Boot 4.1.0 deprecated it with no replacement; the server class carries a deprecation-for-removal warning. The options that remain for editing templates are described on Tooling & IDE Support and in the Boot DevTools reference. The property that matters for templates is spring.thymeleaf.cache, which is true by default and false while developing.

Thymeleaf Layout Dialect 2 to 3 to 4

The Layout Dialect is a separate project and Boot manages its version, so it moves with Boot: 2.x with early Boot 2, 3.0 with late Boot 2.x, 3.x through Boot 3.5 and 4.0, and 4.0 from Boot 4.1.

Table 9. Layout Dialect migration notes
Step Release What changes

2 to 3

3.0.0 (2021-08-28)

Built on Groovy 3, which removes the reflective-access warning on newer JDKs. The deprecated decorator processor and the $DECORATOR_TITLE constant are deleted (use layout:decorate). The head merging strategies that respect the layout’s title position replace the old ones: AppendingStrategy and GroupingStrategy are the new names. layout:collect is deprecated with no replacement. The package moves to nz.net.ultraq.thymeleaf.layoutdialect with an automatic module name; with Boot older than 2.6.0 you must declare the LayoutDialect bean yourself.

3 to 4

4.0.0 (2026-02-28); 4.0.1 (2026-03-25)

Aligned with Spring Boot 4: built for Java 17, Groovy 5 and Gradle 9. The layout:collect processor, deprecated since 3.0, is deleted. The configuration constructors (such as new LayoutDialect(new GroupingStrategy(), false)) are deprecated: the fluent withSortingStrategy(...) and withAutoHeadMerging(...) methods, which already existed before 4.0, are now the preferred way to configure the dialect, and the constructors will be removed in the next major version.

Before: constructor configuration (deprecated in 4.0)
var layoutDialect = new LayoutDialect(new GroupingStrategy(), false);
After: fluent configuration
var layoutDialect = new LayoutDialect()
    .withSortingStrategy(new GroupingStrategy())
    .withAutoHeadMerging(false);

A Boot application usually declares nothing: the auto-configuration registers the dialect when it is on the classpath. Templates that never used layout:collect do not change; how decoration works is on Layouts & the Layout Dialect.

htmx 1 to 2, and 4 in preview

htmx 2.0.0 was published on 2024-06-17 and values backward compatibility, so most pages keep working; the changes below are the ones that need an action.

Table 10. htmx 1.x to 2.x
Change Action

All extensions were removed from the core distribution and are published as separate packages. The sse and ws extensions are htmx-ext-sse and htmx-ext-ws on npm. Legacy hx-ws and hx-sse attributes were replaced by the extensions, and the SSE extension must be the 2.x version.

Load the extension script next to htmx. See Server-Sent Events & WebSockets.

DELETE requests send their parameters in the URL, like GET (htmx.config.methodsThatUseUrlParams includes delete).

Read @RequestParam values, not a form body, on @DeleteMapping handlers, or restore the old behaviour in the configuration.

htmx.config.selfRequestsOnly defaults to true.

Cross-origin requests need it set to false.

hx-on with event names is replaced by hx-on: followed by the kebab-case event name.

Convert hx-on attributes.

Smooth scrolling is no longer the default; Internet Explorer is no longer supported.

Set scrollBehavior to smooth if you want the old default.

The htmx 4 line is published (4.0.0 on 2026-08-28) but only under the npm next tag, and latest stays 2.0.11. Its documentation lists three behaviour changes compared with htmx 2: attribute inheritance becomes explicit (the :inherited modifier, with htmx.config.implicitInheritance restoring the old behaviour), fetch() replaces XMLHttpRequest, and the documentation ships an upgrade-check command-line tool. This section does not teach htmx 4; treat any upgrade as separate from the Thymeleaf work, and pin htmx.org to an explicit version (the Boot BOM does not manage it). The htmx pages of this section cover the 2.x behaviour: htmx Fundamentals.

htmx-spring-boot 3 to 5

htmx-spring-boot (with htmx-spring-boot-thymeleaf, which adds the hx: attribute dialect) tracks Spring Boot, not htmx:

Table 11. Library versions and what they require
Version Date Spring Boot htmx Notable changes

3.2.0

2023-12-14

3.1.x

1.x

Start of the 3.x line in the project’s compatibility table.

3.5.0

2024-08-28

3.2.x

1.x and 2.x

HtmxResponse accepted in the model and as a controller method argument.

4.0.0

2024-11-29

3.4.x

1.x and 2.x

Boot 3.4 baseline. HtmxView deprecated in favour of the Spring Framework 6.2 FragmentsRendering.

5.0.0

2025-11-27

4.0.x

1.x and 2.x

Boot 4 baseline. HtmxView and its return-value handler removed; @HxRefresh, @HxRedirect and @HxLocation removed in favour of view-name prefixes.

5.1.0 to 5.2.0

2026-03-14 to 2026-10-02

4.0.x

1.x and 2.x

Optional swap style, automatic CSRF token injection for the hx: attributes, and (5.2.0) redirect of htmx requests to the current page when the session has expired.

6.0.0

not released

4.1.x

4.x

Targets htmx 4; described only in the project’s main README.

Books and older blog posts show the 3.x builder style. It no longer compiles against 5.2.0, and the replacements are a HtmxResponse argument for headers, view-name prefixes for redirects and refresh, and FragmentsRendering for several fragments.

Before (3.x): builder with several views
return HtmxResponse.builder().view("books/list").view("books/count").build();
Compiler messages on 5.2.0
G.java:2: error: cannot find symbol
  symbol:   method builder()
  location: class HtmxResponse
F.java:1: error: cannot find symbol
  symbol:   class HxRedirect
After (5.x): fragments, a response argument and a redirect prefix
@Controller
class ShelfHtmxController {

    @HxRequest
    @GetMapping("/shelf")
    View shelf(Model model) {
        List<String> titles = List.of("Emma", "Persuasion");
        model.addAttribute("titles", titles);
        model.addAttribute("count", titles.size());
        return FragmentsRendering.with("mig/shelf-list").fragment("mig/shelf-count").build();
    }

    @HxRequest
    @PostMapping("/shelf")
    String add(HtmxResponse htmxResponse, RedirectAttributes redirectAttributes) {
        redirectAttributes.addFlashAttribute("message", "Saved");
        htmxResponse.addTrigger("book-added");
        return "redirect:htmx:/shelf-page";
    }
}
Responses observed through MockMvc
GET /shelf  (HX-Request: true)  ->  200
<ul id="list"><li>Emma</li><li>Persuasion</li></ul>
<span id="count" hx-swap-oob="true">2</span>

POST /shelf (HX-Request: true)  ->  200  HX-Redirect=/shelf-page  HX-Trigger=book-added

The prefixes are redirect:htmx: followed by a path, redirect:htmx:location: followed by a path for a client-side location change, and refresh:htmx; HtmxRedirectView, HtmxLocationRedirectView and HtmxRefreshView are the matching view classes. The project’s compatibility table lists Boot 4.0.x and Java 17 as the minimum for the 5.x line. Details of the annotations, headers and fragment patterns are on htmx with Spring Boot and Fragment Rendering & AJAX.

Where the reference books are outdated

The two books this section consulted, "Taming Thymeleaf" and "Modern Frontends with htmx", are cited on the section’s bibliography. Where a page of this section draws on a book it says what is outdated and shows the current approach; this table collects every case in one place.

Table 12. Outdated in the books, and the current approach on 2026-10-08
Area In the books Current approach

Platform (Taming Thymeleaf)

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

Java 21 or 25, Spring Boot 4.1 (Spring Framework 7, Jakarta EE 11), Thymeleaf 3.1.5, jakarta.*.

Platform (htmx book)

Spring Boot 3.1.4, which is out of support.

Spring Boot 4.1.

Security configuration (Taming Thymeleaf chapter 14)

WebSecurityConfigurerAdapter, antMatchers, @EnableGlobalMethodSecurity, thymeleaf-extras-springsecurity5.

A SecurityFilterChain bean with the lambda DSL, requestMatchers, @EnableMethodSecurity and thymeleaf-extras-springsecurity6 (registered by Boot). See Spring Security Integration.

Layout Dialect (Taming Thymeleaf chapter 6)

Version 2.4.1.

4.0.1, which needs Java 17 and Thymeleaf 3.1; layout:collect is gone and the fluent withSortingStrategy(...) and withAutoHeadMerging(...) methods are preferred over the deprecated constructors.

Expression objects

#request, #response, #session and #servletContext; th:include; th:substituteby; unwrapped fragment expressions.

Removed in 3.1: use model attributes or the param, session and application variables. th:include is deprecated for th:insert, th:substituteby is removed for th:replace, and fragment expressions are written ~{...}. See the sections above.

Front-end tooling (Taming Thymeleaf chapter 4)

Tailwind CSS 2 with the purge option, Gulp, browser-sync, Node 13. The htmx book uses a Tailwind 3 tailwind.config.js.

Tailwind CSS v4: CSS-first configuration (@import "tailwindcss" and @theme, with @source to name extra content paths), driven by its CLI or a bundler. See the Tailwind CSS section of this site.

Alpine.js (Taming Thymeleaf chapters 4 and 13)

Alpine 2: the .away modifier, x-show.transition, a v2 CDN URL.

Alpine 3: @click.outside and x-transition. See Alpine.js & Web Components.

htmx (htmx book)

htmx 1.x.

htmx 2.x: extensions are separate packages (htmx-ext-sse, htmx-ext-ws), DELETE parameters travel in the URL, selfRequestsOnly is true by default, Internet Explorer is dropped. htmx 4 is published only on the next tag; its changes are listed above and not taught here.

htmx-spring-boot (htmx book)

3.x: HtmxResponse.builder() with view(...), annotations such as @HxRedirect.

5.x: HtmxResponse as a method argument, HtmxRedirectView, HtmxLocationRedirectView, HtmxRefreshView, the redirect:htmx: and refresh:htmx view names, and Spring Framework 6.2 FragmentsRendering for several fragments. The HtmxResponse argument is available from 3.5.0.

Testing (Taming Thymeleaf chapter 15)

@MockBean (deprecated in Boot 3.4), com.gargoylesoftware.htmlunit, Cypress 5 (cypress.json, cypress/integration).

@MockitoBean; HtmlUnit under org.htmlunit; Cypress with a cypress.config file and e2e.specPattern (default cypress/e2e), or Playwright; MockMvcTester (AssertJ) is available. See Testing.

Live reload

browser-sync proxies and DevTools LiveReload.

LiveReload is deprecated in Boot 4.1 with no replacement. Use spring.thymeleaf.cache=false, resources served from source, or the dev server of a bundler. See Tooling & IDE Support.

Data

PagingAndSortingRepository also extending CrudRepository; java-faker.

In Spring Data 3 and later PagingAndSortingRepository extends only Repository, so extend ListCrudRepository together with ListPagingAndSortingRepository, or JpaRepository; Hibernate 6 and 7 detect the dialect automatically; Datafaker is the actively developed library for fake data. See Displaying Data, Pagination & Sorting.

Web components (htmx book chapter 10)

Shoelace 2.

Web Awesome, Shoelace’s successor (Web Awesome).

HTTP clients (htmx book chapter 7)

WebClientAdapter.forClient.

WebClientAdapter.create, or the RestClient-backed RestClientAdapter.create in MVC applications.

Deployment

Not covered.

Executable jars and container images (buildpacks or a Dockerfile). See Performance & Production.

Sources for the rows above:

Where the reference books are wrong or unsafe

These are practices in the books' examples that this section does not reproduce. Each row names the problem and the correct form, in prose; the code that demonstrates each correct form is on the page that teaches the topic, mostly Template Security & XSS.

Table 13. Errata and unsafe practices, with the correct form
Where Problem Correct form

User data in JavaScript strings (Taming Thymeleaf chapters 13 and 14)

A user’s full name is interpolated into an Alpine x-data JavaScript literal. It breaks on apostrophes and lets a hostile name inject script.

Pass server values through data-* attributes, which are HTML-escaped and read from JavaScript, or through th:inline="javascript", which serialises and escapes them as valid JavaScript literals.

Unescaped third-party HTML (htmx book chapter 10)

Markdown from GitHub release notes is rendered and output with th:utext without sanitising it.

Sanitise the HTML with a maintained sanitiser (for example the OWASP Java HTML Sanitizer), or escape raw HTML in the Markdown renderer, before any th:utext.

HTML built with string formatting (htmx book chapter 11)

Uploaded file lines are placed into a hand-built div string without escaping.

Render the fragment through Thymeleaf, so th:text escapes the content.

CSRF token in the URL (htmx book chapter 8, with htmx 2)

The trick of including the hidden _csrf field for an hx-delete request now puts the token in the query string, because DELETE parameters travel in the URL in htmx 2.

Send the token in a request header, using the meta tags and the htmx:configRequest event, or the hx: attribute dialect of htmx-spring-boot-thymeleaf, which injects it into hx-headers.

Hard-coded credentials (both books)

In-memory users with literal passwords, and seed users whose password is their first name.

Take credentials from configuration or a secret store, hash with a delegating encoder, and keep any {noop} or {bcrypt} literal inside clearly marked test code.

Markup in message bundles rendered with th:utext (Taming Thymeleaf chapter 10)

Low risk where the bundle is trusted, but the habit generalises badly.

Escape message text by default with th:text, and keep th:utext for markup you control.

Test-only endpoint in src/main with CSRF disabled (Taming Thymeleaf chapter 15)

An endpoint that exists only to reset test data lives in production code with CSRF protection turned off.

Keep such endpoints under a test profile or in test sources, and never ship them: they are an unauthenticated state change.

What is new since the books

None of the following is in either book; each is detailed on the page named next to it.

Table 14. New since the books
Release What you get

Thymeleaf 3.1

jakarta.*, IWebApplication and IWebExchange, th:each over a Stream, custom message resolvers in SpringTemplateEngine, and the general class restrictions with the restricted-mode contexts of Appendix D. See Standalone Engine & Configuration.

Thymeleaf 3.1.3 to 3.1.5

A wider allow-list, Jackson 3 and Java records in JavaScript inlining, and the three security fixes above.

Spring Boot 4

The spring-boot-thymeleaf module and test starter, auto-registered dialects, and the LiveReload deprecation.

Spring Framework 6.2 and later

FragmentsRendering and Collection<ModelAndView> as the way to return several fragments for htmx or Turbo, and for server-sent events.

Layout Dialect 4, htmx 2 (4 in preview) and htmx-spring-boot 5

See the sections above.

The official documentation also covers topics the books do not: the standalone engine, decoupled template logic, th:ref and th:assert, textual template modes, RequestDataValueProcessor, reactive rendering, emails, the extra sec: attributes, the thymeleaf-testing library and writing dialects. They are the subjects of the other pages of this section.

Upgrade checklist

Table 15. Order of work for an old Thymeleaf application
Step Check

Dependencies

Move to Boot 4.1.x (or at least a Boot line that resolves Thymeleaf 3.1.5). Replace spring-boot-starter-web with spring-boot-starter-webmvc, add the test starters, and use thymeleaf-extras-springsecurity6.

Imports

Replace javax.* with jakarta.*; compile.

Security

Replace the adapter subclass with a SecurityFilterChain bean, antMatchers with requestMatchers, and the global method security annotation with @EnableMethodSecurity.

Templates, search

Search for #request, #response, #session, #servletContext, execInfo, th:substituteby, th:include, th:insert or th:replace values without the tilde envelope, and T( or JDK class calls in expressions.

Templates, run

Render every page in a test and compare. A removed attribute does not fail; it silently drops the fragment.

Layout Dialect

Remove layout:collect; replace constructor configuration with the fluent methods.

htmx

Add the extension packages you used, review DELETE handlers, and replace HtmxResponse.builder() and the removed annotations in controllers.

Tests

Replace @MockBean and @SpyBean; use @MockitoBean and @MockitoSpyBean.

Security release

Confirm with mvn dependency:tree that Thymeleaf resolves to 3.1.5 or later.

References