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.
| Component | Version | Notes |
|---|---|---|
Thymeleaf core, |
3.1.5.RELEASE (2026-04-21) |
3.1.5 is the minimum version this section teaches. There is no |
|
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 Layout Dialect |
4.0.1 (2026-03-25) |
Built for Java 17 and Groovy 5. |
htmx |
2.0.11 is npm |
This section teaches htmx 2. |
|
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. |
Source: Thymeleaf — GitHub releases
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).
| 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 |
Set |
Inlining |
|
Remove |
Textual modes |
New syntax for TEXT, JAVASCRIPT and CSS templates: element-like |
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 |
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 |
Enable it on the resolver. See Natural Templates & Decoupled Logic. |
Dialect API |
A new, smaller |
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 |
|
Use |
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.
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);
<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.
<!-- 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>
<head>
<title>Shelf</title>
<link rel="stylesheet" href="/css/site.css">
<link rel="stylesheet" href="/css/shelf.css">
</head>
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>
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.
| Area | Change in 3.1 | What you do |
|---|---|---|
Jakarta namespace |
Support for the Servlet API 5.0 |
Use Spring Boot 3 or later with |
Spring and Spring Security modules |
New |
Replace |
Java baseline |
Java 8 is the minimum for core; Java 17 for the |
Plan the JDK upgrade with the Boot upgrade. |
|
The |
Remove |
Web API abstraction |
|
Only standalone servlet code changes; see the example below. |
Removed expression objects |
|
Expose what the template needs as model attributes, or use the |
|
Still works in 3.1.5, but it will be removed, and |
Use |
Unwrapped fragment expressions deprecated |
|
Wrap every fragment expression. |
|
The attribute, deprecated since 3.0, is gone. |
Use |
Restrictions |
A general ban on calling methods, constructors and static references of classes in |
Move the logic to a bean or a model attribute. See Template Security & XSS. |
Smaller changes |
|
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>
<div id="main" th:include="mig/frag :: frag">placeholder</div>
<p id="main" th:substituteby="~{mig/frag :: frag}">placeholder</p>
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>
| Template | Rendered |
|---|---|
|
|
|
|
|
|
|
|
|
The host tag keeps its |
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:
<p th:text="${#request.contextPath}">/ctx</p>
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:
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>
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.
<p th:text="${T(java.lang.Runtime).getRuntime().availableProcessors()}">n</p>
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>
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:
import javax.servlet.http.HttpServletRequest;
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:
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);
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.
| Release | Date | Notable changes |
|---|---|---|
3.1.0.RELEASE |
2022-11-17 |
Jakarta, |
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 |
3.1.2.RELEASE |
2023-07-30 |
AttoParser 2.0.7; static access to |
3.1.3.RELEASE |
2024-12-09 |
Calling methods of |
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 |
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 |
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.
| 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.
| 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 |
| Step | Changes that touch Thymeleaf applications |
|---|---|
Boot 2 to Boot 3 |
Java 17 minimum; Jakarta EE 10, so |
Boot 3.x |
Since 3.4 the Boot |
Boot 3 to Boot 4 |
Spring Framework 7 and Spring Security 7; auto-configuration is split into modules, and Thymeleaf’s moves to a |
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
<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.
Source: Spring Boot 4.0 Migration Guide
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:
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
class B extends WebSecurityConfigurerAdapter { }
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:
@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:
import org.springframework.boot.test.mock.mockito.MockBean;
class C { @MockBean Object service; }
C.java:1: error: package org.springframework.boot.test.mock.mockito does not exist
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.
Source: Spring Boot — Developer Tools
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.
| 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 |
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 |
var layoutDialect = new LayoutDialect(new GroupingStrategy(), false);
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.
| Change | Action |
|---|---|
All extensions were removed from the core distribution and are published as separate packages. The |
Load the extension script next to htmx. See Server-Sent Events & WebSockets. |
|
Read |
|
Cross-origin requests need it set to |
|
Convert |
Smooth scrolling is no longer the default; Internet Explorer is no longer supported. |
Set |
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.
Source: htmx 1.x to 2.x migration guide
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:
| 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 |
|
4.0.0 |
2024-11-29 |
3.4.x |
1.x and 2.x |
Boot 3.4 baseline. |
5.0.0 |
2025-11-27 |
4.0.x |
1.x and 2.x |
Boot 4 baseline. |
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 |
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.
return HtmxResponse.builder().view("books/list").view("books/count").build();
G.java:2: error: cannot find symbol
symbol: method builder()
location: class HtmxResponse
F.java:1: error: cannot find symbol
symbol: class HxRedirect
@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";
}
}
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.
| Area | In the books | Current approach |
|---|---|---|
Platform (Taming Thymeleaf) |
Java 11, Spring Boot 2.3, Thymeleaf 3.0, |
Java 21 or 25, Spring Boot 4.1 (Spring Framework 7, Jakarta EE 11), Thymeleaf 3.1.5, |
Platform (htmx book) |
Spring Boot 3.1.4, which is out of support. |
Spring Boot 4.1. |
Security configuration (Taming Thymeleaf chapter 14) |
|
A |
Layout Dialect (Taming Thymeleaf chapter 6) |
Version 2.4.1. |
4.0.1, which needs Java 17 and Thymeleaf 3.1; |
Expression objects |
|
Removed in 3.1: use model attributes or the |
Front-end tooling (Taming Thymeleaf chapter 4) |
Tailwind CSS 2 with the |
Tailwind CSS v4: CSS-first configuration ( |
Alpine.js (Taming Thymeleaf chapters 4 and 13) |
Alpine 2: the |
Alpine 3: |
htmx (htmx book) |
htmx 1.x. |
htmx 2.x: extensions are separate packages ( |
|
3.x: |
5.x: |
Testing (Taming Thymeleaf chapter 15) |
|
|
Live reload |
browser-sync proxies and DevTools LiveReload. |
LiveReload is deprecated in Boot 4.1 with no replacement. Use |
Data |
|
In Spring Data 3 and later |
Web components (htmx book chapter 10) |
Shoelace 2. |
Web Awesome, Shoelace’s successor (Web Awesome). |
HTTP clients (htmx book chapter 7) |
|
|
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.
| 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 |
Pass server values through |
Unescaped third-party HTML (htmx book chapter 10) |
Markdown from GitHub release notes is rendered and output with |
Sanitise the HTML with a maintained sanitiser (for example the OWASP Java HTML Sanitizer), or escape raw HTML in the Markdown renderer, before any |
HTML built with string formatting (htmx book chapter 11) |
Uploaded file lines are placed into a hand-built |
Render the fragment through Thymeleaf, so |
CSRF token in the URL (htmx book chapter 8, with htmx 2) |
The trick of including the hidden |
Send the token in a request header, using the meta tags and the |
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 |
Markup in message bundles rendered with |
Low risk where the bundle is trusted, but the habit generalises badly. |
Escape message text by default with |
Test-only endpoint in |
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.
| Release | What you get |
|---|---|
Thymeleaf 3.1 |
|
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 Framework 6.2 and later |
|
Layout Dialect 4, htmx 2 (4 in preview) and |
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
| Step | Check |
|---|---|
Dependencies |
Move to Boot 4.1.x (or at least a Boot line that resolves Thymeleaf 3.1.5). Replace |
Imports |
Replace |
Security |
Replace the adapter subclass with a |
Templates, search |
Search for |
Templates, run |
Render every page in a test and compare. A removed attribute does not fail; it silently drops the fragment. |
Layout Dialect |
Remove |
htmx |
Add the extension packages you used, review |
Tests |
Replace |
Security release |
Confirm with |