Fragments
|
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 (2026-04-21), Spring Boot 4.1.1 (Spring Framework 7.0.9) and Java 21, checked on 2026-10-08, and
every example was compiled and run on that stack in a scratch Bookshelf application (Maven wrapper, JDK 26 with --release 21, the
auto-configured SpringTemplateEngine and MockMvc); the rendered output shown is the real output of those runs. Fragments belong to the Standard Dialect, so everything
here applies to Thymeleaf standalone, with Spring MVC and with Spring WebFlux alike; the examples use the Spring dialect (SpEL), and the
only differences from the standalone engine (OGNL) are the expression language and the Spring-only features named where they appear. The
running scenario is the Bookshelf introduced on the Getting Started page.
A fragment is a piece of a template that other templates can include: a footer, a book card, a form field. Fragments are Thymeleaf’s native answer to "don’t repeat yourself" in markup. They are resolved while the including template is parsed and processed, they need no extra dependency, and they keep both templates valid, viewable HTML. How whole pages are assembled from fragments (layouts) is the subject of Layouts & the Layout Dialect, and a larger library of components built from fragments is on Reusable UI Components.
Defining and including a fragment
A fragment is defined by putting th:fragment on the element that should be reusable. The attribute value is the fragment’s name, optionally followed by a
parameter list (see Parameterised fragments). The definition itself stays in an ordinary template, so a file can hold any number of
fragments next to normal page content.
<footer th:fragment="copy" id="site-footer" class="foot">© 2026 Bookshelf</footer>
Another template includes it with th:insert or th:replace, whose value is a fragment expression (see the next section). The two attributes
differ in what happens to the host element:
-
th:insertkeeps the host element and inserts the fragment inside it. -
th:replacereplaces the host element with the fragment. -
th:includeis liketh:insertbut inserts only the fragment’s content, without the fragment’s own element. It is deprecated since Thymeleaf 3.1 and logs a warning at run time (Deprecated attribute {th:include,data-th-include} found ... Please use {th:insert,data-th-insert} instead). Useth:insertorth:replace. Its siblingth:substitutebyhas been removed.
<div th:insert="~{frag/components :: copy}"></div>
<div th:replace="~{frag/components :: copy}"></div>
<div th:include="~{frag/components :: copy}"></div>
Rendered:
<div><footer id="site-footer" class="foot">© 2026 Bookshelf</footer></div>
<footer id="site-footer" class="foot">© 2026 Bookshelf</footer>
<div>© 2026 Bookshelf</div>
The three outputs match the three attributes: the host <div> survives around the fragment, disappears in favour of it, or survives around only the fragment’s
text. Remember that th:fragment itself is removed from the output, and so are all the th:* attributes it contains once processed.
footer th:fragment=copy"] frag --> ins["th:insert on a div
div wraps footer"] frag --> rep["th:replace on a div
footer only, div gone"] frag --> inc["th:include on a div, deprecated
div wraps the footer's content"]
When a template is meant to be included wholesale, no th:fragment is needed at all: ~{frag/tiny} (a template name with no selector) includes the whole template.
<div th:insert="~{frag/tiny}"></div>
Rendered: <div><span>tiny</span></div> (the trailing newline of the included file is kept).
An included fragment is processed in the context of the including template, so it sees the model of the request, the local variables defined around the
host element (th:with, the iteration variable of an enclosing th:each) and, in Spring, the same beans and messages. This is how the same fragment can be used
inside a loop to render one card per book.
The fragment expression syntax
The value of th:insert and th:replace is a fragment expression (a Standard Expression, the same family as variable and link expressions; see
Standard Expression Syntax). Its forms are:
| Expression | Meaning |
|---|---|
|
The part of the template named |
|
The whole template. |
|
A part of the current template. |
|
The empty fragment: nothing (see The empty fragment and the no-op token). |
The template name is resolved by the template resolvers exactly like a view name, so frag/components is templates/frag/components.html with the default
prefix and suffix of Spring Boot (spring.thymeleaf.prefix and spring.thymeleaf.suffix). Both parts can be expressions, so a selector can be computed (~{templates/layout :: ${section}}), but
template and fragment names are evaluated in restricted expression mode (U Appendix D), and a name or selector must never be built from request
input; see Pitfalls.
The same syntax also works for a fragment of the current template, which makes a template self-referential in a useful way:
<div th:replace="~{this :: #local}"></div>
<div th:replace="~{:: #local}"></div>
<p id="local">Local fragment</p>
Both lines render <p id="local">Local fragment</p>.
Before Thymeleaf 3.0 the expression was written without ~{ }, as in the following line. That unwrapped form still works, but it is
deprecated and logs Deprecated unwrapped fragment expression ... Please use the complete syntax of fragment expressions instead; it will be removed. Use the
~{...} form everywhere, and note that a fragment expression is also a normal expression value, which is what makes conditional insertion and parameters possible.
<div th:insert="frag/components :: copy"></div>
Markup selectors
The part after :: is a markup selector. Thymeleaf borrows the syntax from its parser, AttoParser, and it resembles XPath, CSS and jQuery. A plain name such as
copy selects an element that has th:fragment="copy" (or th:ref="copy"); everything else selects by tag, class, id or attribute, which lets you include a
part of a template that has no th:fragment at all (typical when you include a block of a page from another template or a test). All selectors are
absolute: they are computed from the root of the template named on the left of ::, even when that template is the current one.
| Selector | Selects |
|---|---|
|
An element with |
|
Any tag that has |
|
The element with |
|
Any element whose |
|
A |
|
Attribute operators |
|
Position among siblings. The index is zero-based in the 3.1.5 run below ( |
Run against a small source template with a content wide div, a side div, a list of two links (one with class="external") and a <section th:ref="promo">:
<x th:replace="~{frag/sel-src :: //div[@class='side']}"></x>
<x th:replace="~{frag/sel-src :: div.content}"></x>
<x th:replace="~{frag/sel-src :: a.external[@href^='https']}"></x>
<x th:replace="~{frag/sel-src :: li[1]}"></x>
<x th:replace="~{frag/sel-src :: promo}"></x>
<x th:replace="~{frag/sel-src :: %promo}"></x>
<x th:replace="~{frag/sel-src :: .wide}"></x>
Rendered:
<div class="side">Side</div>
<div class="content wide"><p>First</p></div>
<a class="external" href="https://example.org">Org</a>
<li><a href="/local">Local</a></li>
<section><b>Promo</b></section>
<section><b>Promo</b></section>
<div class="content wide"><p>First</p></div>
Two details deserve attention. First, .myfrag is a class selector, while myfrag looks for a th:fragment/th:ref named myfrag; mixing them up is the
commonest reason a selector "finds nothing". Second, the tutorial shows the short form div.content as equivalent to //div[@class='content']. A fragment selected
by selector alone, with no th:fragment, is also what th:ref (introduced for the decoupled logic on
Natural Templates & Decoupled Logic) is for: th:ref gives an element a selector name without making
it a declared fragment.
Parameterised fragments
A fragment can declare parameters, which makes it a small function of markup. The declaration is a parenthesised list after the name, and the call passes values either by position or by name.
<div th:fragment="book-card(book, showAuthor)" class="card"
th:assert="${!#strings.isEmpty(book.title)}">
<h3 th:text="${book.title}">Dune</h3>
<p th:if="${showAuthor}" th:text="${book.author}">Frank Herbert</p>
</div>
<div th:replace="~{frag/components :: book-card(${book}, true)}"></div>
<div th:replace="~{frag/components :: book-card(showAuthor=false, book=${book})}"></div>
Rendered, for a book titled Dune by Frank Herbert:
<div class="card">
<h3>Dune</h3>
<p>Frank Herbert</p>
</div>
<div class="card">
<h3>Dune</h3>
</div>
-
With named arguments the order does not matter (
showAuthor=false, book=${book}). With positional arguments the order is the declaration order. -
The arguments are evaluated in the including template and bound as local variables of the fragment, with the usual scoping of
th:with(see Local Variables, Precedence, Comments & Blocks). -
A parameter can be a literal (
true,'Ana'), a variable expression (${book}) or another fragment expression (next section).
Fragment-local variables without a signature
A fragment does not have to declare its parameters. Even when the definition has no parameter list, the call can still pass named values; the tutorial
calls them fragment local variables without fragment arguments. The call syntax is the named form, and the effect is the same as wrapping the include in a
th:with.
<p th:fragment="greeting" th:text="|Hello, ${who}!|">Hello, reader!</p>
<div th:replace="~{frag/components :: greeting(who='Ana')}"></div>
Rendered: <p>Hello, Ana!</p>. This is convenient for a quick include and keeps the prototype text valid when the fragment file is opened alone. The price is that
the fragment’s contract is implicit, so prefer a declared signature for anything reused widely.
Assertions with th:assert
th:assert takes a comma-separated list of expressions that must all evaluate to true; if one is false, processing stops with a TemplateAssertionException. It is
the way to make a fragment’s expectations about its parameters explicit and to fail fast and legibly instead of rendering half a card. In the card above the
assertion requires a non-empty title.
<div th:replace="~{frag/components :: book-card(${emptyBook}, true)}"></div>
The failure, as it appears at the end of the exception chain:
org.thymeleaf.exceptions.TemplateAssertionException: Assertion '${!#strings.isEmpty(book.title)}' not valid in template 'frag/components', line 8 col 6
The outer exceptions are TemplateInputException and TemplateProcessingException, wrapping the assertion, because the fragment is processed while the host template is parsed. th:assert can
sit on any element, not only on fragments; it simply belongs naturally on the first element of a fragment.
Flexible layouts: passing markup, the empty fragment and the no-op token
Because a fragment expression is itself a value, you can pass markup to a fragment as an argument. The fragment decides where to put it, usually with th:replace
on an element of its own: that element is the default content, and the argument replaces it. The tutorial’s example is a common <head>, to which each page passes its own
title and extra links.
<head th:fragment="common_header(title, links)">
<title th:replace="${title}">Bookshelf</title>
<link rel="stylesheet" href="/css/site.css" th:href="@{/css/site.css}">
<th:block th:replace="${links}" />
</head>
A page passes its own <title> and <link> elements, selected from itself with ~{::title} and ~{::link}:
<head th:replace="~{frag/components :: common_header(~{::title}, ~{::link})}">
<title>Dune - Bookshelf</title>
<link rel="stylesheet" th:href="@{/css/books.css}" href="/css/books.css">
</head>
Rendered:
<head>
<title>Dune - Bookshelf</title>
<link rel="stylesheet" href="/css/site.css">
<link rel="stylesheet" href="/css/books.css">
</head>
The page stays a valid prototype (it has its own <head>), while the real <head> comes from the shared fragment. This pattern is the heart of the native
approach to layouts that the next page compares with the Layout Dialect.
The empty fragment and the no-op token
Two special values let a caller say "nothing" in two different ways:
-
~{}is the empty fragment: it really renders nothing, so the element it would replace is removed. -
_is the no-operation token: the instruction is skipped as if the attribute were absent, so the element keeps its default prototype content.
A box fragment with an optional footer shows both. The footer element holds the default markup and is replaced by the argument.
<section th:fragment="box(title, footer)" class="box">
<h2 th:text="${title}">Title</h2>
<footer th:replace="${footer}">Default footer</footer>
</section>
<div th:replace="~{frag/components :: box('News', _)}"></div>
<div th:replace="~{frag/components :: box('News', ~{})}"></div>
Rendered:
<section class="box">
<h2>News</h2>
<footer>Default footer</footer>
</section>
<section class="box">
<h2>News</h2>
</section>
Use _ to mean "use the default" and ~{} to mean "none". Both are valid in other expressions too (_ as a branch of a conditional or an Elvis expression).
Conditional insertion
Since fragment expressions are values, they can be the branches of a conditional or Elvis expression, which gives conditional insertion without a separate th:if and
without deleting the host element’s prototype content when the condition is false.
<div th:insert="${admin} ? ~{frag/components :: copy} : ~{}"></div>
<div th:insert="${!admin} ? ~{frag/components :: copy} : ~{}">x</div>
<div th:insert="${nothing} ?: ~{frag/components :: copy}">x</div>
Rendered, with admin true and nothing null:
<div><footer id="site-footer" class="foot">© 2026 Bookshelf</footer></div>
<div></div>
<div><footer id="site-footer" class="foot">© 2026 Bookshelf</footer></div>
In the second line the false branch is the empty fragment, so th:insert inserts nothing and the host <div> stays, empty. If you want the host element gone too, use
th:if on it, or th:replace with ~{}. The no-op token _ can be used as a branch instead of ~{} when the host’s prototype content should stay in place for the false case.
Removing template fragments with th:remove
The opposite of including is dropping prototype-only markup. th:remove is the attribute for it and is the last step of the attribute precedence (see
Local Variables, Precedence, Comments & Blocks). It takes one of five values:
| Value | Effect |
|---|---|
|
Removes the host element and its children |
|
Removes the children but keeps the host element |
|
Removes the host element but keeps the children |
|
Removes all children of the host except the first one. This keeps one mock row for the designer and drops the rest |
|
Does nothing; useful as a conditional value ( |
<table>
<tr th:each="b : ${books}"><td th:text="${b.title}">Dune</td></tr>
<tr th:remove="all"><td>Mock row</td></tr>
<tr th:remove="all"><td>Mock row 2</td></tr>
</table>
<div th:remove="tag" class="wrapper">kept body</div>
<div th:remove="body" class="wrapper2">dropped</div>
Rendered, with one book:
<table>
<tr><td>Dune</td></tr>
</table>
kept body
<div class="wrapper2"></div>
Pitfalls
| Symptom | Cause | Fix |
|---|---|---|
|
The template name in the fragment expression does not resolve (wrong folder, missing file, resolver prefix or suffix) |
Use the path relative to the resolver prefix, without the suffix; check |
|
The template exists but the selector matches nothing (typo in the fragment name, |
Fix the name; remember a bare name is a |
Two fragments with the same name both appear |
A selector selects every match, and nothing enforces unique fragment names in one file |
Keep fragment names unique per template; the run with two |
|
A |
Pass what the signature expects, or loosen the assertion |
Deprecation warnings for |
The old |
Use |
A fragment name or parameter containing |
Template and fragment names, and fragment parameters, are evaluated in restricted expression mode (U Appendix D) |
Compute such values in the controller and put them in the model |
A different template is rendered depending on a request parameter |
A template name built from user input, for example |
Never build template names or selectors from request data; map a validated key to a fixed name in Java |
The included part keeps its own |
The selector matched a whole element tree or the entire template |
Select a smaller part, or mark the fragment on an element such as |
The fragment cannot see a variable of the including template |
The variable is local to a different element, or the name is shadowed by a fragment parameter |
Pass the value as an argument, or define it on an enclosing element |