前回は「セッション管理」について学びました。セッションにより複数のHTMLページでデータを保持することができ、ログインや買い物かごの機能が実現できました。
今回はThymeleafを使いデータをHTMLテンプレートで動的に表示する方法を学びましょう。
今回の学習の重点をオレンジの枠線で図示します。

1. なぜThymeleaf(テンプレートエンジン)が必要なのか?
Thymeleafを使うと、HTMLファイルの中にStandard Expression(標準式)や制御構文を書くことで、Javaのオブジェクトの値を動的に埋め込んだり、条件分岐や繰り返し表示ができます。
なぜThymeleaf?
- デザイナーとの協業がしやすい:HTMLファイルをそのままブラウザでプレビューしやすい
- Spring Bootとの相性が良い
2. Thymeleafの基本
ThymeleafではHTMLタグ属性に対してth:*
を使います。
2.1. 変数展開の基本
HTMLの中に以下のように記述します。
<p th:text="${msg}">ここにメッセージが入ります</p>
${msg}
→ コントローラからmodel.addAttribute("msg", "Hello")
された文字列などを埋め込みます。
Controller側
package com.example.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class MainController{
@GetMapping("/")
public String sample(Model model) {
model.addAttribute("msg", "Hello World");
return "sample";
}
}
sample.html(抜粋)
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"></head>
<body>
<p th:text="${msg}">ここにメッセージ</p>
</body>
</html>

これで /
にアクセスすると <p>Hello World</p>
が表示されます。
"ここにメッセージ" は、通常 表示されない 文字列です。なぜなら、Thymeleafの th:text
属性は、指定された変数 ${msg}
の値でタグの内容を置き換えるからです。つまり、msg
に値がある場合は、<p>
の中身が msg
の値に置き換えられ、元の "ここにメッセージ"
は消えてしまいます。
2.2 [[${msg}]]
を使った場合
次に、[[${msg}]]
記法を使った例を示します。
<p>[[${msg}]]</p>
この書き方では、[[${msg}]]
の位置に${msg}
の値が挿入されます。
HTMLエスケープ処理が行われ、安全に変数を表示できます。
sample.html(抜粋)
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
</head>
<body>
<p>[[${msg}]]</p>
</body>
</html>
この場合も /
にアクセスすると、以下のように表示されます。
<p>Hello World</p>
このように、th:text
属性と[[${msg}]]
は同じ結果になりますが、[[${msg}]]
記法の場合は、HTMLタグの中のプレースホルダーの位置に変数の値を展開する形になります。
th:text
→ タグ内の内容を丸ごと置換する
[[${msg}]]
→ タグ内の指定位置に挿入する
本研修では、th:text
を優先して使っていきます。
2.4 ==
で文字列の比較ができる
Thymeleafの式内で、文字列を比較する場合は、JavaSEとは異なり、==
で比較可能です。なお文字列リテラルは '(シングルクォーテーション)で囲む必要があります。
※ここから先はThymeleafと対応するHTMLを上下に並べて配置しますので見比べて下さい。
<p th:text="${msg == 'Hello World' }">メッセージがHello Worldかどうか?</p>
<p>true</p> <!--msgがHello Worldの場合 -->
ポイント:
- Thymeleafでは内部で
equals()
による比較を自動で行うため、
Javaのようにequals()
を呼ぶ必要はありません。 - ただし、
equals()
を明示的に呼ぶことも可能です。
<p th:text="${msg.equals('Hello World') }">メッセージがHello Worldかどうか?</p>
<p>true</p> <!--msgがHello Worldの場合 -->
2.5 数値の比較 (==
)
数値についても同様に比較可能です。
<p th:text="${age == 20 }">ageが20かどうか?</p>
<p>false</p> <!--ageが19の場合 -->
数値の場合、以下のような大小比較も簡単に行えます。
<p th:text="${age <= 20 }">ageが20以上かどうか?</p>
<p>true</p> <!--ageが19の場合 -->
3. Thymeleafでの条件分岐・繰り返し
3.1 条件分岐
th:if
, th:unless
Thymeleafで単純なif/else
を実現するには、th:if
と th:unless
を使うのが最も簡単です。
<p th:if="${age >= 20}">お酒が飲めます</p>
<p th:unless="${age >= 20}">まだ飲めません</p>
<p>まだ飲めません</p> <!--ageが19の場合 -->
th:if="${条件式}"
→ 条件式がtrue
ならタグを表示、false
なら非表示th:unless="${条件式}"
→th:if
の逆で、条件式がfalse
なら表示、true
なら非表示
参考(JavaSE)
int age = 19; // 任意の年齢を設定
if (age >= 20) {
System.out.println("お酒が飲めます");
} else {
System.out.println("まだ飲めません");
}
3.2 null の扱いについて
変数がnull
かどうかは、Thymeleafでは以下のように記述します。
nullの場合を判定
<p th:if="${user == null}">ユーザーが存在しません。</p>
<p>ユーザーが存在しません。</p> <!--userがnullの場合 -->
空文字の扱いについて
文字列が空文字かどうかの判定もよく行います。
<p th:if="${name == ''}">名前が入力されていません</p>
<p>名前が入力されていません</p> <!--nameが空文字の場合 -->
また、空文字やnullをまとめて判定するには以下の書き方が便利です。
<!-- 文字列がnullまたは空文字の場合 -->
<p th:if="${#strings.isEmpty(name)}">名前が未入力です</p>
#strings
はThymeleafが提供する便利なユーティリティオブジェクトです。
3.3 th:switch
/ th:case
JavaSEのswitchと同じことをするには、Thymeleafの th:switch
/ th:case
を使います。
<div th:switch="${bloodType}">
<p th:case="'A'">慎重な性格</p>
<p th:case="'B'">大胆な性格</p>
<p th:case="'O'">おおらかな性格</p>
<p th:case="*">不思議な性格</p> <!-- default相当 -->
</div>
<div>
<p>不思議な性格</p> <!-- default相当 -->
</div>
th:switch="${変数}"
th:case="'A'"
→A
に一致する場合のみ表示th:case="*"
→ マッチしない場合(デフォルト)
参考(JavaSE)
String bloodType = "A"; // 任意の血液型を設定
switch (bloodType) {
case "A":
System.out.println("慎重な性格");
break;
case "B":
System.out.println("大胆な性格");
break;
case "O":
System.out.println("おおらかな性格");
break;
default:
System.out.println("不思議な性格");
break;
}
3.4 繰り返し
JavaSEの拡張for文に相当するのは、th:each
属性です。
<ul>
<li th:each="name : ${names}" th:text="${name}">名前</li>
</ul>
<ul>
<li>imai</li>
<li>matsuda</li>
<li>tabuchi</li>
<li>hashi</li>
</ul>
<!-- namesが4人の名前の場合 -->
${names}
は、Controller側でList<String>
などをmodel.addAttribute("names", namesList)
しておく。name : ${names}
→ 変数name
にリストの要素が順番に入るth:text="${name}"
→ 文字列を表示
例)オブジェクトのリストをテーブル表示
<table>
<thead>
<tr><th>ID</th><th>名前</th><th>価格</th></tr>
</thead>
<tbody>
<tr th:each="car : ${cars}">
<td th:text="${car.id}"></td>
<td th:text="${car.name}"></td>
<td th:text="${car.price}"></td>
</tr>
</tbody>
</table>
参考(JavaSE)
for (Car car : cars) {
System.out.printf(car.getId(), car.getName(), car.getPrice());
}
3.5 th:eachとth:ifの組み合わせ
例えば、名前が「tabuchi」の場合のみ表示する、というケースを考えてみます。
<ul>
<li th:each="name : ${names}" th:if="${name == 'tabuchi'}">
[[${name}]]
</li>
</ul>
<ul>
<li>tabuchi</li>
</ul>
<!-- namesが4人の名前の場合 -->
参考(JavaSE)
for (String name : names) {
if ("tabuchi".equals(name)) {
System.out.println(name);
}
}
4. 文字列のフォーマット(金額、日付など)
Thymeleafでは#numbers
や #temporals
(Springのユーティリティオブジェクト)などを使って書式制御ができます。
4.1 金額の書式
<p th:text="${#numbers.formatCurrency(price)}"></p>
<p>¥2,590,000</p>
金額の表示方法は複数ありますが、ここでは整数表示し、3桁区切りはLocale設定による#numbers.formatCurrency(value)
を使いました。
#numbers.formatCurrency(value)
はロケール設定といって、「表示形式や言語・地域に応じたデータの表示ルールを決めるための設定」に左右されます。
ロケール設定は特に数字・通貨・日付などを適切なフォーマットで表示したい場合に重要になります。
例えば、ロケールが日本語なら、日本円として表示されます(¥1,000)、ロケールが米国なら、ドル表記 $1,000 のようになります。
ロケールの設定は本研修の範囲を超えるため、ここでは割愛します。
4.2 日付の書式
<p th:text="${#temporals.format(date, 'yyyy年MM月dd日(E)')}"></p>
Thymeleaf の #temporals.format(date, 'yyyy年MM月dd日(E)')
を使うと、2025年03月16日(日)
のように、「年」「月」「日」「曜日」を日本語表記 で表示できます。
例)Controllerで渡す
@GetMapping("/dateSample")
public String dateSample(Model model) {
model.addAttribute("today", LocalDate.now());
return "dateSample";
}
dateSample.html
<p th:text="${#temporals.format(today, 'yyyy年MM月dd日(E)')}"></p>
<p>202X年03月16日(日)</p>
各フォーマット要素の意味
フォーマット | 意味 | 出力例(2025年3月16日) |
---|---|---|
yyyy | 西暦4桁 | 202X |
MM | 月(2桁) | 03 |
dd | 日(2桁) | 16 |
E | 曜日(省略形) | 日 |
EEEE | 曜日(フル表記) | 日曜日 |
具体例
フォーマット | 出力例(202X年3月16日) |
---|---|
yyyy/MM/dd | 202X/03/16 |
yyyy年M月d日 | 202X年3月16日(先頭ゼロなし) |
yyyy-MM-dd (E) | 202X-03-16 (日) |
yyyy/MM/dd (EEEE) | 202X/03/16 (日曜日) |
5. CSS・JavaScriptに変数の値を渡す
5.1 CSSに変数を渡す
CSSに変数を渡す最もシンプルな方法はThymeleafで以下のようにHTMLの属性として「th:style=」を設定する方法です。
<body th:style="'background-color: ' + ${bgColor}">
以前フォームのところで学んだ天気予報プログラムのプロトタイプを例にCSSに変数の値を渡す例を説明します。天気によって背景色を変えたいとします。晴れなら"yellow"、雨なら"blue"、曇りなら"gray"とします。

入力フォーム
<a th:href="@{/weather(weather='晴れ')}">晴れ</a>
<a th:href="@{/weather(weather='曇り')}">曇り</a>
<a th:href="@{/weather(weather='雨')}">雨</a>
Controller
package com.example.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class WeatherController {
@GetMapping("/weather")
public String weather(@RequestParam(name = "weather") String weather, Model model) {
// 天気に応じて背景色を設定
String backgroundColor;
switch (weather) {
case "曇り":
backgroundColor = "gray";
break;
case "雨":
backgroundColor = "blue";
break;
default: // 晴れ
backgroundColor = "yellow";
break;
}
// Modelに値をセット
model.addAttribute("weather", weather);
model.addAttribute("bgColor", backgroundColor);
return "weather";
}
@GetMapping("/link")
public String showLink() {
return "link";
}
}
HTML内で変数を埋め込む
<body th:style="'background-color: ' + ${bgColor}">
今は <span th:text="${weather}"></span> なんですね。
</body>
5.2 JavaScriptに変数を渡す
JavaScriptのconfirm()メソッドを使ってログアウトの確認をする例で説明します。

Controller
@GetMapping("/home")
public String home(Model model) {
String username = "yusei"; // ここでは仮のユーザー名
model.addAttribute("username", username);
return "home"; // home.html を表示
}
@GetMapping("/logout")
public String logout() {
return "redirect:/home"; // ログアウト後、ホームへリダイレクト
}
コメントアウトしたうえで変数を埋め込む
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Home</title>
</head>
<body>
<h1>ようこそ、<span th:text="${username}"></span> さん!</h1>
<!-- ログアウトリンク -->
<a href="#" onclick="confirmLogout()">ログアウト</a>
<!-- JavaScript に変数を渡す -->
<script th:inline="javascript">
var username = /*[[${username}]]*/ 'ゲスト';
function confirmLogout() {
var isConfirmed = confirm(username + " さん、本当にログアウトしますか?");
if (isConfirmed) {
window.location.href = "/logout";
}
}
</script>
</body>
</html>
解説
<script th:inline="javascript">
Thymeleaf で th:inline="javascript" を指定すると、Thymeleaf の式 (${}) を JavaScript のコード内で適切に埋め込むための処理 が行われます。
var username = /*[[${username}]]*/ 'ゲスト';
Thymeleaf の JavaScript 内埋め込みの仕組み
/*[[${username}]]*/
は、Thymeleaf が HTML をレンダリングするときに、${username}
の値を JavaScript の適切な形式で埋め込むための構文です。/*[[${username}]]*/
のように コメントアウトの中に Thymeleaf の式を書いている理由 は、Thymeleaf を処理しない環境(ローカルのHTMLファイルとして開いた場合など)でもエラーが発生しないようにするためです。'ゲスト'
は、デフォルト値として設定されています。${username}
がnull
や空文字
だった場合に、適切なデフォルト値として「ゲスト」を使うため です。
6.Thymeleaf を使った共通レイアウトの作成
th:fragment
を使うことで、ヘッダーやフッターなどの共通部分を別のテンプレートとして定義し、複数のページで再利用できます。
以下に、main.html
(メインレイアウト)、header.html
(ヘッダー)、footer.html
(フッター)の3つのファイルを用いた実装方法を説明します。

※サンプルは「http://localhost:8080/replace」で実行できます。
※メニューの「会社概要」や「お問い合わせ」のリンク先は未作成です。
ヘッダーファイル
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
</head>
<body>
<header th:fragment="header">
<h1>サイトのタイトル</h1>
<nav>
<ul>
<li><a th:href="@{/}">ホーム</a></li>
<li><a th:href="@{/about}">会社概要</a></li>
<li><a th:href="@{/contact}">お問い合わせ</a></li>
</ul>
</nav>
</header>
</body>
</html>
th:fragment="header"
で、この <header>
を再利用可能な部品(フラグメント)として定義しています。英語の【fragment】には「断片」といった意味があります。
フッターファイル
<!-- src/main/resources/templates/footer.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
</head>
<body>
<footer th:fragment="footer">
<p>Copyright © Say consulting group, Inc All Rights Reserved.</p>
</footer>
</body>
</html>
th:fragment="footer"
で、フッターを再利用可能な部品として定義しています。
メインレイアウト
<!-- src/main/resources/templates/main.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>メインページ</title>
</head>
<body>
<!-- ヘッダーの挿入 -->
<header th:replace="header :: header"></header>
<!-- メインコンテンツ -->
<main>
<p>ここにメインコンテンツが入ります。</p>
</main>
<!-- フッターの挿入 -->
<footer th:replace="footer :: footer"></footer>
</body>
</html>
th:replace="header :: header"
でheader.html
のth:fragment="header"
を挿入。th:replace="footer :: footer"
でfooter.html
のth:fragment="footer"
を挿入。- ヘッダー・フッターを共通化し、メンテナンスしやすくなる。
結合後のHTML
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>メインページ</title>
</head>
<body>
<!-- ヘッダーの挿入 -->
<header>
<h1>サイトのタイトル</h1>
<nav>
<ul>
<li><a href="/">ホーム</a></li>
<li><a href="/about">会社概要</a></li>
<li><a href="/contact">お問い合わせ</a></li>
</ul>
</nav>
</header>
<!-- メインコンテンツ -->
<main>
<p>ここにメインコンテンツが入ります。</p>
</main>
<!-- フッターの挿入 -->
<footer>
<p>Copyright © Say consulting group, Inc All Rights Reserved.</p>
</footer>
</body>
</html>
第6章の今回はHTMLに動きを与える方法について学びました。
第7章は「DTOとは?データの受け渡しを整理し、コードをシンプルにする」です。
