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">&copy; 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:insert keeps the host element and inserts the fragment inside it.

  • th:replace replaces the host element with the fragment.

  • th:include is like th:insert but 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). Use th:insert or th:replace. Its sibling th:substituteby has 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">&copy; 2026 Bookshelf</footer></div>
<footer id="site-footer" class="foot">&copy; 2026 Bookshelf</footer>
<div>&copy; 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.

flowchart LR frag["Fragment in components.html
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

~{templatename::selector}

The part of the template named templatename that matches the markup selector. A fragment name is a valid selector, and spaces around :: are optional.

~{templatename}

The whole template.

~{::selector} or ~{this::selector}

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

copy

An element with th:fragment="copy" or th:ref="copy", or a tag named copy; x is the same as //x

%copy

Any tag that has th:ref="copy" or th:fragment="copy"; footer%copy limits it to <footer>

#site-footer

The element with id="site-footer"; footer#site-footer limits it to <footer>

.foot

Any element whose class contains the value foot; footer.foot limits it to <footer>. The class attribute is multivalued, so div.two matches <div class="one two three">

//div[@class='content'], div[@class='content']

A <div> whose class attribute is exactly content, at any depth. n/x selects direct children x of n, n//x descendants at any depth

a.external[@href^='https']

Attribute operators =, !=, ^= (starts with) and $= (ends with); @ is optional (x[z='v'] is x[@z='v']). Several conditions are joined with and or by chaining [ ][ ]

ul/li[1]

Position among siblings. The index is zero-based in the 3.1.5 run below (li[0] is the first item and li[2] finds nothing in a two-item list), although the tutorial text says only "positioned in number i"

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">&copy; 2026 Bookshelf</footer></div>
<div></div>
<div><footer id="site-footer" class="foot">&copy; 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

all

Removes the host element and its children

body

Removes the children but keeps the host element

tag

Removes the host element but keeps the children

all-but-first

Removes all children of the host except the first one. This keeps one mock row for the designer and drops the rest

none

Does nothing; useful as a conditional value (th:remove="${keep} ? 'none' : 'all'")

<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

TemplateInputException: Error resolving template [frag/nope], template might not exist or might not be accessible by any of the configured Template Resolvers

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 spring.thymeleaf.prefix

TemplateInputException: Error resolving fragment: "~{'frag/components' :: nosuch}": template or fragment could not be resolved

The template exists but the selector matches nothing (typo in the fragment name, .name used for a fragment, or an id that is not there)

Fix the name; remember a bare name is a th:fragment/th:ref match and .name is a class match

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 th:fragment="x" elements rendered both of them

TemplateAssertionException: Assertion ... not valid

A th:assert in the fragment failed, usually because the caller passed null or an empty value

Pass what the signature expects, or loosen the assertion

Deprecation warnings for th:include and for th:insert="tpl :: frag"

The old th:include and the unwrapped fragment syntax are deprecated and will be removed

Use th:insert/th:replace with ~{tpl :: frag}. th:substituteby no longer exists

A fragment name or parameter containing ${param.x}, new ... or T(...) fails

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 ~{templates/${param.page} :: content}, lets a visitor choose which template is included

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 <html> or <head>

The selector matched a whole element tree or the entire template

Select a smaller part, or mark the fragment on an element such as <div> or <footer>

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