When building web interfaces with Spring Boot, sending collection data from a controller to a view template is a common task. Thymeleaf handles this with a single attribute that cycles through every item in the collection you provide.
What happened
- The th:each attribute cycles through any collection the controller exposes.
- String collections render each value directly without additional accessors.
- Object collections use dot notation to pull individual properties.
- Tabular layouts typically repeat a single row element per record.
- Spring Boot configures Thymeleaf automatically when the library is present.
Our analysis
This iteration pattern is the backbone of Thymeleaf list rendering. The syntax stays consistent whether you display text labels or structured records; only how you reference values changes inside the loop.
Who should care:
- Developers building dashboards or tabular UIs** — a single attribute replaces manual rendering loops.
- Spring Boot practitioners** — no extra configuration is needed to start using this.
- Beginners to Thymeleaf** — collections are a forgiving starting point because the current item is always available as the loop variable.
The trade-off: property names resolve when the page loads, not during compilation. An error like a misspelled property will only appear at runtime. On the positive side, Thymeleaf exposes loop metadata (position, parity, boundaries) when you ask for it, which is useful for alternating row colors or conditional separators.
A worked example
Controllers attach data to the model using a name-value approach. Both string collections and object collections follow the same controller-side pattern.
Simple string collection** — render each entry as a separate list entry:
“`
<ol>
<li th:each="item : ${fruits}">
<span th:text="${item}"></span>
</li>
</ol>
“`
Object collection** — display two properties per record in table rows:
“`
<table border="1">
<thead>
<tr>
<th>Identifier</th>
<th>Label</th>
</tr>
</thead>
<tbody>
<tr th:each="record : ${employees}">
<td th:text="${record.id}"></td>
<td th:text="${record.name}"></td>
</tr>
</tbody>
</table>
“`
The expression "localName : ${collectionName}" binds each element to a temporary variable as Thymeleaf walks the collection. Within the loop body, ${record.name} reaches into the name property of the current object using Spring Expression Language.
Background you can use
Thymeleaf processes templates on the server before sending HTML to the browser. It integrates tightly with Spring MVC, meaning model attributes are available without extra translation. Drop the dependency into a Spring Boot project and the framework handles the rest, looking for template files in the resources folder by convention.
What to watch next
Request a second variable alongside your loop variable to access loop metadata. This gives you the current position, total count, and flags like first/last or even/odd without additional logic. When working with map structures, the iteration syntax requires two variables instead of one.
Key takeaways
- Th:each covers every common list iteration scenario in Thymeleaf.
- Reference object properties with dot notation inside the loop body.
- Controller code does not change between string lists and object lists.
- Spring Boot configures Thymeleaf automatically.
- Loop metadata variables unlock position-aware rendering.
Frequently asked questions
What is the basic syntax for looping in Thymeleaf?
Place th:each on the HTML element you want repeated. The pattern is th:each="localVar : ${modelAttribute}", where localVar holds the current item during each pass.
How do I display a simple string list?
Add the list to the model, then use th:text="${localVar}" inside a th:each loop. The variable contains the string value directly.
How do I access properties of objects in a collection?
Use dot notation within the loop. If your loop variable is person, write ${person.firstName} to display that property. This works for any Java object the model exposes.
Can I know which iteration I am on?
Declare a second variable in the attribute: th:each="entry, pos : ${collection}". The pos variable provides index, count, total size, and boolean flags like first, last, even, and odd.
Does this work with Maps?
Yes, but you need two loop variables to capture the key and the value separately, and the template syntax adjusts accordingly.