Let's get started with a Microservice Architecture with Spring Cloud:
How to Put a Text Without Removing HTML Structures in Thymeleaf
Last updated: February 11, 2026
1. Overview
When working with the Thymeleaf template engine, it’s easy to unintentionally break an HTML structure while rendering dynamic content. This usually occurs when attribute-based expressions replace an element’s content, leading to incomplete output.
In this tutorial, we’ll walk through a common mistake developers make when rendering dynamic text in Thymeleaf. Also, we’ll explore two effective approaches to preserve the intended HTML structure: using text inlining and rendering dynamic content in a child element, with th:remove=”tag” used as an optional markup refinement.
2. Project Setup
To begin, let’s bootstrap a Spring Boot project by adding the spring-boot-starter-web dependency to our pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>4.0.2</version>
</dependency>
The dependency allows us to write a Spring web application with an embedded web server.
Also, let’s add the spring-boot-starter-thymeleaf dependency to our pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>4.0.2</version>
</dependency>
The spring-boot-starter-thymeleaf dependency allows us to use the Thymeleaf template engine.
Finally, let’s create a controller class that handles requests to the home page and exposes the title and subtitle to the view:
@Controller
public class HomeController {
@GetMapping("/home-page")
public String homePage(Model model) {
String title = "Introduction to Java";
String subtitle = "[Java powers over 1 billion devices]";
model.addAttribute("title", title);
model.addAttribute("subtitle", subtitle);
return "structures/home";
}
}
In the code above, we define a controller method mapped to the /home-page endpoint. When a GET request is made, the method returns the home view and exposes the title and subtitle values as the model attributes. These attributes can then be accessed in the Thymeleaf template for rendering.
3. The Common Mistake: Breaking HTML Structure
Moving on, let’s create a home.html file in the template directory and use Thymeleaf expressions to display the title and subtitle:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
</head>
<body>
<h1 th:text="${title}" >
title
<small th:text="${subtitle}" >subtitle</small>
</h1>
</body>
</html>
In the code above, we attempt to render both the title and subtitle model attributes inside an <h1> element. The subtitle is wrapped in a <small> tag to distinguish it from the main title visually.
At first glance, this approach seems perfect. However, when we compile our application and visit the URL – http://localhost:8080/home-page to see the result, we notice an unexpected result:
In the image above, Thymeleaf only renders the title and omits the subtitle because applying th:text=”${title}” expression to the <h1> element causes Thymeleaf to remove everything inside the <h1>, including the <small> tag that contains the subtitle. Hence, only the title is rendered in the final HTML.
4. Using th:inline=’text’
To avoid the break in HTML structure, we can use Thymeleaf’s text inlining feature to render dynamic values directly inside an element’s body.
Let’s demonstrate it by rewriting the body section of our HTML code:
<body>
<h1 th:inline="text" >
[[${title}]]
<small th:text="${subtitle}">subtitle</small>
</h1>
</body>
Here, we explicitly enable text inlining by adding th:inline=”text” to the <h1> element. This allows us to use the [[ …]] inline expression syntax to insert the value of title directly into the element’s content.
Notably, text inlining is enabled by default when Thymeleaf processes templates in HTML mode. As a result, we can use the [[…]] inline expression without explicitly declaring th:inline=”text” on the element:
<body>
<h1>
[[${title}]]
<small th:text="${subtitle}">subtitle</small>
</h1>
</body>
However, explicit inlining is required when working inside script or style tags, where the processing mode must be specified.
Here’s the new rendered page:
In the image above, both title and subtitle are rendered correctly. This works because the title is rendered using an inline expression, which preserves the surrounding HTML structure and prevents the subtitle from being omitted.
5. Preserving HTML Structure by Rendering Text in a Child Element
Alternatively, we can solve this problem by applying th:text to a child element rather than the parent <h1> element. This approach preserves the HTML structure and prevents child elements from being removed:
<body>
<h1>
<span th:text="${title}" th:remove="tag">title</span>
<small th:text="${subtitle}" >subtitle</small>
</h1>
</body>
Here, we wrap the title in a <span> element. Since th:text replaces only the contents of the span, it doesn’t affect the child nodes of the <h1> element. As a result, the subtitle remains intact.
The th:remove=”tag” attribute is optional and is used to remove the <span> element itself after Thymeleaf evaluates its content. This allows us to keep the final rendered HTML clean while still avoiding the original structural issue.
If we inspect the rendered HTML, we get the following output:
<body>
<h1>
Introduction to Java
<small >[Java powers over 1 billion devices]</small>
</h1>
</body>
Here, the <span> element is removed from the final output because of the th:remove=”tag” attribute. Thymeleaf evaluates the content of the <span> and then removes the element itself, leaving only its text.
If th:remove=”tag” is omitted, the rendered HTML will still include the <span> element, although the page will render correctly in both cases.
6. Conclusion
In this article, we learned the common pitfalls that can unintentionally break HTML structure when working with the Thymeleaf template engine. Also, we demonstrate the error and how to resolve it using Thymeleaf’s th:inline=”text” attribute or by rendering dynamic text in a child element, both of which allow us to render dynamic content while preserving the intended HTML markup.
As always, the complete source code for the example is available over on GitHub.
















