Thymeleaf th:eachでループ処理を簡単に行う方法!初心者でもわかるテンプレートエンジン入門
新人
「先輩、Thymeleafで繰り返し処理をするときにth:eachを使うって聞いたんですが、どうやって使えばいいんですか?」
先輩
「良いね。Thymeleaf th:eachはループ処理を簡単に書ける便利な属性なんだ。例えばリストや配列をHTMLで表示したいときに使えるよ。」
新人
「Javaのfor文みたいに書けるってことですか?」
先輩
「そうそう!それじゃあ、基本から順番に見ていこう。」
1. Thymeleafとは?
Thymeleafは、Javaと組み合わせて使えるテンプレートエンジンの一つで、特にSpring MVCやSpring Bootでよく利用されます。静的なHTMLに動的なデータを埋め込むことができ、学習コストが低いのが特徴です。
従来のJSPよりも直感的に扱えるため、初心者がWebアプリケーションを学習するときにぴったりです。特にThymeleaf th:eachを使ったループ処理は、リストや配列を簡単に画面に表示することができるため、多くの場面で利用されます。
また、ThymeleafはHTMLファイルをそのままブラウザで確認できる「ナチュラルテンプレート」という特徴を持っています。これによりデザイナーと開発者が同じファイルを扱えるので、チーム開発でも効率的です。
2. th:eachとは?
Thymeleaf th:eachは「ループ処理を簡単に行うための属性」です。Javaでいうfor文や拡張for文と同じ役割を持っていて、リストや配列に入っている要素を1つずつ取り出してHTMLに表示できます。
例えば商品リストやユーザー一覧を作る場合、1つずつ書いていくのは大変ですが、Thymeleaf th:eachを使うと数行でまとめることができます。変数名を自由に設定できるので、見やすく管理できる点も魅力です。
また、ループ中のインデックスを取得する機能もあるため、番号付きリストやテーブルを作るときにも役立ちます。初心者のうちからしっかり理解しておくと、今後の開発で必ず役立ちます。
3. 簡単なループ処理の例
ここではThymeleaf th:eachを使ったシンプルな例を紹介します。開発環境はpleiadesを使い、ビルドツールはGradleを利用する前提です。コントローラは@Controllerを使って記述します。
@Controller
public class ItemController {
@GetMapping("/items")
public String items(Model model) {
List<String> itemList = Arrays.asList("りんご", "みかん", "ぶどう");
model.addAttribute("items", itemList);
return "items";
}
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>商品リスト</title></head>
<body>
<h3>商品一覧</h3>
<ul>
<li th:each="item : ${items}" th:text="${item}">商品</li>
</ul>
</body>
</html>
商品一覧
・りんご
・みかん
・ぶどう
この例では、コントローラで作成したリストをビューに渡し、Thymeleaf th:eachを使って繰り返し処理を行っています。結果としてリストの中身が1つずつ表示され、動的な商品一覧ページが完成します。
初心者はまずこのような基本例から始めると、ループ処理の流れが直感的に理解できるでしょう。
4. リストや配列をth:eachでループ処理する方法
Thymeleaf th:eachはリストや配列を順番に取り出して表示するのに非常に便利です。Javaで用意した配列やリストを@Controllerからビューに渡し、そのデータをHTMLで一行ずつ展開できます。これにより、コードの記述量を減らしつつ、画面に動的な一覧を表示できます。
例えば「商品名」を配列に入れて、それをブラウザでリスト表示することを考えてみましょう。Javaのfor文をHTMLに直接書くことはできませんが、Thymeleaf th:eachを使うと自然に表現できます。
@Controller
public class ArrayController {
@GetMapping("/array")
public String array(Model model) {
String[] fruits = {"バナナ", "いちご", "メロン"};
model.addAttribute("fruits", fruits);
return "array";
}
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>配列のループ</title></head>
<body>
<h3>配列から取り出した果物一覧</h3>
<ul>
<li th:each="f : ${fruits}" th:text="${f}">果物</li>
</ul>
</body>
</html>
配列から取り出した果物一覧
・バナナ
・いちご
・メロン
このように配列やリストをそのまま渡しても、Thymeleaf th:eachが自動的にループ処理して表示してくれます。Java側で複雑な処理を記述しなくても、ビュー側でシンプルにデータを展開できる点が大きな魅力です。
5. th:eachでループ中の変数を扱う(indexやcountの使い方)
Thymeleafのループ処理では、ただ要素を取り出すだけでなく、ループ中の番号や合計件数を扱うこともできます。これを実現するのがindexやcountといった変数です。
indexは0から始まる番号を表し、countは1から始まる番号を表します。つまり、テーブルの行番号やリストの通し番号を表示したいときに役立ちます。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>ループ変数</title></head>
<body>
<h3>ループ中の番号を表示する例</h3>
<table border="1">
<tr>
<th>No</th>
<th>名前</th>
</tr>
<tr th:each="user, stat : ${users}">
<td th:text="${stat.count}">1</td>
<td th:text="${user}">名前</td>
</tr>
</table>
</body>
</html>
ここではuser, stat : ${users}のように書いています。前半のuserは要素そのもの、後半のstatはループの状態を保持するオブジェクトです。stat.countやstat.indexを利用することで、簡単に番号付きの一覧を作ることができます。
初心者が一覧ページを作るときには、このループ変数をうまく活用すると、より見やすい画面を作ることができます。
6. @Controllerから渡されたリストをビューに表示する具体例
次に、実際に@ControllerでJavaのリストを作成し、ビューに渡してThymeleaf th:eachで表示する流れを確認してみましょう。ここではユーザー名のリストを例にします。
@Controller
public class UserController {
@GetMapping("/users")
public String users(Model model) {
List<String> userList = Arrays.asList("山田太郎", "佐藤花子", "鈴木一郎");
model.addAttribute("users", userList);
return "users";
}
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>ユーザー一覧</title></head>
<body>
<h3>ユーザー一覧</h3>
<ul>
<li th:each="u, stat : ${users}">
<span th:text="${stat.count}">1</span> :
<span th:text="${u}">ユーザー名</span>
</li>
</ul>
</body>
</html>
ユーザー一覧
1 : 山田太郎
2 : 佐藤花子
3 : 鈴木一郎
この例では、@Controllerで用意したuserListをビューに渡し、Thymeleaf th:eachでループ処理しています。ループ変数stat.countを使って番号を表示し、ユーザー名と一緒に出力しています。
このように、Spring MVCとThymeleafを組み合わせることで、Javaのリストを画面に直感的に表示できます。特にThymeleaf th:eachはリストや配列をループ処理するのに最適で、初心者でも理解しやすい書き方です。
動的な一覧表示やテーブル作成の際に欠かせない機能なので、実際に自分でコードを書いて動作を確認してみると理解が深まるでしょう。
7. th:eachと他のth属性(th:ifやth:text)との組み合わせ例
ここではThymeleaf ループ処理の中で頻繁に登場する条件分岐と文字列出力の基本的な組み合わせを丁寧に確認します。単純に一覧を回して表示するだけでは現実の画面要件に足りないことが多く、たとえば在庫の有無によって文言を切り替えたり、価格が特定のしきい値を超えたときに注意文を表示したりといった細かな制御が必要になります。Thymeleaf th:eachにth:ifとth:textを組み合わせると、テンプレートの中だけで読みやすく宣言的に要件を表現できます。重要なのは、テキストの出力は基本的にth:textに任せて、条件分岐はth:ifを用いるという役割分担を崩さないことです。これにより、値の差し込みと表示制御が混ざらず、初学者でも変更点を追いやすくなります。また、否定条件が読みづらくなる場合はth:unlessを用いるのが素直です。ここでは価格の条件と在庫の条件を同時に扱う小さな例を用意し、ひとつの行の中で複数の属性を使い分ける流れを体験してみましょう。なお、開発はpleiadesとGradleを前提とし、コントローラは@Controllerを用います。
@Controller
public class ProductController {
@GetMapping("/price-list")
public String priceList(Model model) {
java.util.List<Item> list = java.util.List.of(
new Item("ノート", 120, true),
new Item("高級万年筆", 9800, false),
new Item("消しゴム", 80, true)
);
model.addAttribute("items", list);
model.addAttribute("limit", 1000);
return "price-list";
}
static class Item {
String name; int price; boolean stock;
Item(String n, int p, boolean s){ this.name=n; this.price=p; this.stock=s; }
public String getName(){ return name; }
public int getPrice(){ return price; }
public boolean isStock(){ return stock; }
}
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>価格と在庫の表示</title></head>
<body>
<h3>価格一覧</h3>
<table border="1">
<thead><tr><th>商品名</th><th>価格</th><th>在庫</th><th>注意</th></tr></thead>
<tbody>
<tr th:each="it : ${items}">
<td th:text="${it.name}">名称</td>
<td th:text="${it.price}">0</td>
<td>
<span th:if="${it.stock}" th:text="'在庫あり'">在庫あり</span>
<span th:unless="${it.stock}" th:text="'在庫なし'">在庫なし</span>
</td>
<td>
<em th:if="${it.price >= limit}" th:text="'高価格帯のため購入前に確認してください'">注意</em>
<span th:unless="${it.price >= limit}" th:text="'特記事項なし'">特記事項なし</span>
</td>
</tr>
</tbody>
</table>
</body>
</html>
価格一覧
ノート 120 在庫あり 特記事項なし
高級万年筆 9800 在庫なし 高価格帯のため購入前に確認してください
消しゴム 80 在庫あり 特記事項なし
この例ではThymeleaf th:eachで行を繰り返しながら、同じ行の中でth:textとth:if、さらにth:unlessを適切に分担させています。条件に合う要素だけが最終HTMLに残るため、不要な要素がブラウザに出力されることはありません。複雑な要件ほどロジックをテンプレートに集中させず、コントローラで値を用意し、テンプレートでは「表示の最終判断だけ」を行うようにすると保守性が高まります。これがThymeleaf ループ処理における基本的な設計指針です。
8. th:eachでのネストしたループ(入れ子リストの表示例)
次に、階層構造のデータを扱う場面を想定したThymeleaf th:eachの入れ子表現を学びます。カテゴリとそのカテゴリに属する商品という定番の構造を例に取り、外側のリストを回しながら、内側のサブリストをさらに回すという二重のThymeleaf ループ処理を丁寧に組み立てます。入れ子ではループ変数の名前が衝突しないように慎重に付け分けること、外側の要素が空のときの見せ方を用意しておくこと、そして内側の一覧が空でも画面が崩れないようにフォールバック表示を置くことが重要です。特に初学者は「外側の各行の中に内側のulやtableが存在する」というDOM上の階層と、テンプレートのループ構造が一致しているかを意識すると理解が早まります。
@Controller
public class CategoryController {
@GetMapping("/categories")
public String categories(Model model) {
java.util.List<CategoryVm> cats = java.util.List.of(
new CategoryVm("文具", java.util.List.of("ノート", "消しゴム")),
new CategoryVm("家電", java.util.List.of("電気ケトル")),
new CategoryVm("食品", java.util.List.of())
);
model.addAttribute("categories", cats);
return "categories";
}
static class CategoryVm {
String name; java.util.List<String> products;
CategoryVm(String n, java.util.List<String> p){ this.name=n; this.products=p; }
public String getName(){ return name; }
public java.util.List<String> getProducts(){ return products; }
}
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>カテゴリと商品</title></head>
<body>
<h3>カテゴリ別商品一覧</h3>
<div th:if="${#lists.isEmpty(categories)}">
<p>カテゴリがありません</p>
</div>
<div th:unless="${#lists.isEmpty(categories)}">
<section th:each="cat, cstat : ${categories}">
<h4>
<span th:text="${cstat.count}">1</span>.
<span th:text="${cat.name}">カテゴリ名</span>
</h4>
<ul th:if="${#lists.isEmpty(cat.products)}">
<li>登録された商品がありません</li>
</ul>
<ul th:unless="${#lists.isEmpty(cat.products)}">
<li th:each="p, pstat : ${cat.products}">
<span th:text="${pstat.count}">1</span> :
<span th:text="${p}">商品名</span>
</li>
</ul>
</section>
</div>
</body>
</html>
カテゴリ別商品一覧
1. 文具
1 : ノート
2 : 消しゴム
2. 家電
1 : 電気ケトル
3. 食品
登録された商品がありません
ここでは外側のThymeleaf th:eachでカテゴリを回し、内側のThymeleaf th:eachで商品を回しています。番号付けにはそれぞれのループ状態変数のcountを活用し、読み手に分かりやすい順番を示しています。入れ子の深さが増えると視認性が落ちやすいため、ループごとに見出しや区切りを配置すると可読性が保てます。また、外側・内側のいずれが空でも破綻しないようにth:ifとth:unlessでフォールバック文言を入れておくと、実運用のデータにばらつきがあっても安定して表示できます。これが現場でのThymeleaf ループ処理における堅牢な組み立て方です。
9. 初心者におすすめの練習方法(簡単な表示切り替えや商品リスト画面を作ってみるなど)
最後に、学んだThymeleaf th:eachを自分の手で確実に使いこなすための練習手順を示します。短い反復で段階的に難易度を上げ、毎回「モデルに何を入れて、テンプレートでどのように参照したか」をメモしておくと、理解が定着します。第一段階は固定配列の出力、第二段階はインデックス表示、第三段階は条件分岐の追加、第四段階で入れ子リストという順番で進めると挫折しにくく、成功体験を積み重ねやすい構成になります。とくに否定条件や空リスト時の扱いを早めに取り入れると、実務に近い挙動を早期に学べます。以下は小さな練習用の画面構成例です。フォームからのキーワードで商品を絞り込み、結果が空なら案内を表示、結果があれば番号付きで一覧化するという、現場でよくある要件を凝縮しています。
@Controller
public class PracticeController {
@GetMapping("/practice")
public String practice(@RequestParam(value = "q", required = false) String q, Model model) {
java.util.List<String> all = java.util.List.of("りんご", "みかん", "ぶどう", "バナナ");
java.util.List<String> filtered;
if (q == null || q.isBlank()) {
filtered = all;
} else {
filtered = all.stream().filter(s -> s.contains(q)).toList();
}
model.addAttribute("query", q);
model.addAttribute("results", filtered);
return "practice";
}
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>練習:検索と一覧</title></head>
<body>
<h3>検索結果</h3>
<!-- 入力の有無を案内 -->
<p th:if="${#strings.isEmpty(query)}">キーワード未入力のため全件を表示します</p>
<p th:unless="${#strings.isEmpty(query)}" th:text="'検索語:' + ${query}">検索語</p>
<!-- 結果の有無で切り替え -->
<div th:if="${#lists.isEmpty(results)}">
<p>該当する商品がありません</p>
</div>
<ul th:unless="${#lists.isEmpty(results)}">
<li th:each="name, st : ${results}">
<span th:text="${st.count}">1</span> :
<span th:text="${name}">商品名</span>
</li>
</ul>
</body>
</html>
検索結果
キーワード未入力のため全件を表示します
1 : りんご
2 : みかん
3 : ぶどう
4 : バナナ
この練習では、Thymeleaf th:eachでの一覧化、th:textでの安全な文字列出力、th:ifとth:unlessでの状態切り替えという三つの柱を一度に体験できます。加えて、検索語が空かどうか、結果リストが空かどうかという二種類の「空」の扱いを明確に分けており、テンプレート側での判定ロジックの置き方を意識できる構成になっています。慣れてきたら入れ子のカテゴリ構成に発展させ、カテゴリごとに小見出しを付け、各カテゴリの中でさらにThymeleaf ループ処理を行う練習に進みましょう。テンプレートは常に読み手を意識し、変数名とループ状態変数名を衝突させない、条件分岐は見通しの良い形に保つ、空のときの見せ方を必ず用意する、という三点を守ると、規模が大きくなっても迷子になりません。日々の反復と小さな成功体験の積み重ねが、Thymeleaf th:eachを使いこなす最短ルートになります。
まとめ
Thymeleaf th:eachで学んだループ処理の基本と実践
この記事では、Thymeleafの中でも特に使用頻度が高いth:eachを中心に、テンプレートエンジンによるループ処理の考え方と実装方法を丁寧に見てきました。 ThymeleafはSpring BootやSpring MVCと組み合わせて使われることが多く、HTMLをそのまま活かしながら動的な表示を実現できる点が大きな特徴です。 その中でth:eachは、Javaで用意したリストや配列、コレクションをHTML側で展開するための要となる機能でした。
基本的な使い方としては、th:each="変数 : ${リスト}"という形で記述し、Javaの拡張for文と同じ感覚で要素を一つずつ取り出して表示します。
これにより、商品一覧やユーザー一覧、カテゴリ別リストなど、Webアプリケーションで頻繁に登場する画面を、少ない記述でわかりやすく構築できます。
特に初心者にとっては、「Java側でデータを用意し、表示の責務はビューに任せる」という役割分担を自然に学べる点が重要です。
また、ループ変数として利用できるindexやcountを活用することで、番号付きの一覧表示やテーブル行番号の出力も簡単に行えることを確認しました。
これらの機能を知っているかどうかで、画面の見やすさや実装のしやすさが大きく変わります。
単に要素を並べるだけでなく、「何番目のデータか」を表示できるようになると、実務に近い画面を作れるようになります。
条件分岐や入れ子ループとの組み合わせが理解の鍵
th:each単体だけでなく、th:ifやth:unless、th:textと組み合わせることで、 より実践的なテンプレートが書ける点も重要な学習ポイントでした。 在庫の有無による表示切り替え、価格条件による注意文の表示、データが存在しない場合のフォールバック表示など、 現場でよくある要件をテンプレート内で自然に表現できるようになります。
さらに、カテゴリと商品一覧のような入れ子構造のループ処理を学ぶことで、 複数階層のデータを安全かつ読みやすく表示する考え方も身につきました。 外側と内側でループ変数名を明確に分け、空リスト時の表示も忘れずに用意することで、 実データにばらつきがあっても壊れない画面を作れるようになります。 これはThymeleafに限らず、テンプレートエンジン全般で役立つ重要な設計視点です。
まとめとしてのサンプルプログラム
最後に、この記事全体の内容を踏まえた、シンプルで実務を意識したサンプルを確認してみましょう。
@Controllerで用意したリストをビューに渡し、th:eachと条件分岐を使って一覧表示を行います。
@Controller
public class SummaryController {
@GetMapping("/summary")
public String summary(Model model) {
java.util.List<String> names = java.util.List.of("田中", "佐藤", "鈴木");
model.addAttribute("names", names);
return "summary";
}
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>まとめ表示</title></head>
<body>
<h3>名前一覧</h3>
<ul>
<li th:each="n, st : ${names}">
<span th:text="${st.count}">1</span> :
<span th:text="${n}">名前</span>
</li>
</ul>
</body>
</html>
このような基本形をしっかり理解しておくことで、Thymeleaf th:eachを使ったループ処理は怖くなくなります。 小さなサンプルを何度も書き、動かし、結果を確認することが上達への近道です。
生徒
「th:eachって最初は難しそうに見えましたけど、 Javaのfor文と同じ考え方だと思うと一気に理解しやすくなりました。」
先生
「そうだね。テンプレート側では表示に集中して、 データの準備はコントローラに任せるのがポイントだよ。」
生徒
「indexやcountを使えば、番号付きの一覧も簡単に作れるのが便利でした。 実際の画面を想像しながら書くと理解が深まりますね。」
先生
「その通り。さらにth:ifやth:unlessと組み合わせると、 実務で使える画面に一気に近づくよ。」
生徒
「入れ子のループも最初は混乱しましたが、 変数名を分けるだけで読みやすくなるのが印象的でした。」
先生
「良い気づきだね。Thymeleaf th:eachを使いこなせるようになると、 Spring Bootでの画面開発が一気に楽しくなるはずだよ。」