Stop Repeating Your HTML: Implementing Template Inheritance with Thymeleaf Layout Dialect
Stop repeating headers and footers in every HTML file. Learn how to use the Thymeleaf Layout Dialect to implement a decorator pattern for cleaner, maintainable Spring Boot templates.
23 Aug 2026, 01:25 UTC

The Maintenance Trap of Fragment Inclusion
When building a Spring Boot application, the initial instinct for UI consistency is often th:replace or th:insert. You create a header.html and a footer.html, then manually pull them into every page. This works for a few pages, but as the app grows every new view repeats head tags, script imports and structural divs.
Changing a meta tag or CSS framework then requires edits across many templates. The useful shift is from a pull model with inclusion to a push model with decoration, where a child page defines its content and pushes it into a pre-defined master shell.
Decorator Pattern with Layout Dialect
The Thymeleaf Layout Dialect implements a decorator pattern. A base layout defines the shell and marks insertion points with layout:fragment. Child templates declare layout:decorate="~{layout/main}" and supply fragments for those placeholders.
This centralizes structural logic. The base layout owns <head>, navigation and footer, while individual views focus only on unique content.
Implementation
Dependency and Dialect Registration
Add the layout dialect extension and register it so the TemplateEngine can resolve layout: attributes. Verify the artifact resolves in your build and the bean is present in the context.
<dependency>
<groupId>nz.net.ultraq.thymeleaf</groupId>
<artifactId>thymeleaf-layout-dialect</artifactId>
<version>3.x</version>
</dependency>
@Bean
public LayoutDialect layoutDialect() {
return new LayoutDialect();
}
Base Layout
Create src/main/resources/templates/layout/main.html. The layout defines fragments with default content.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<title>My App - <span layout:fragment="title">Default Title</span></title>
<link rel="stylesheet" th:href="@{/css/main.css}" />
</head>
<body>
<nav>Common Navigation</nav>
<main layout:fragment="content">
<p>Default content replaced by child.</p>
</main>
<footer>© 2026 Company Inc.</footer>
</body>
</html>
Child Page
The child declares which layout to decorate and provides fragments.
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout/main}">
<head>
<title layout:fragment="title">User Profile</title>
</head>
<body>
<div layout:fragment="content">
<h1>Welcome to your profile</h1>
<p>User-specific data goes here.</p>
</div>
</body>
</html>
Trade-offs and Limitations
- External dependency risk. Version mismatch between the Layout Dialect and Thymeleaf core can cause runtime exceptions during fragment resolution.
- Processing overhead. Decoration adds a small resolution step before rendering. Negligible for most apps, measurable under extreme traffic.
- Hierarchy complexity. Mixing native
th:replacefragments withlayout:decorateand over-nesting layouts complicates debugging and increases latency.
Verification
Check the result without relying on visual CSS masking.
- Inspect rendered HTML source to confirm
<nav>and<footer>from the base layout appear with the child<h1>. - Rename a child fragment to a non-matching name. The base default should render, confirming decoration is active.
- With MockMvc, assert the response contains both static layout text like "Common Navigation" and dynamic child text like "Welcome to your profile".
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.