Thymeleafのth:valueで送ることができる情報・できない情報を新人エンジニア向けに解説
こんにちは。ゆうせいです。
今回は、Thymeleafのth:valueで「送ることができる情報」と「送ることができない情報」について解説します。
まず大前提として、th:valueは「サーバーへ送信する命令」ではありません。
th:valueは、HTMLのvalue属性に値を埋め込むためのThymeleaf属性です。
つまり、Thymeleafがサーバー側でHTMLを作るときに、Java側の値をHTMLのvalueへ差し込む役割を持ちます。
そして、実際にサーバーへ送信するかどうかは、HTMLフォームの仕組みで決まります。
Thymeleaf公式ドキュメントでも、th:fieldはinput、select、textareaなどに応じて動作し、input[type=text]ではid、name、th:valueを持つinputに近い形になると説明されています。また、th:valueでレンダリングされる値はHTMLエスケープされます。
新人エンジニア向けに一言でまとめると、th:valueは「送信する値の中身を作る係」です。
でも、実際に送信されるためには、name属性が必要です。
たとえるなら、th:valueは荷物の中身です。
name属性は宛名です。
中身だけあっても、宛名がなければ相手に届きません。
th:valueの基本
まず、基本の例を見てみましょう。
<input type="text" name="carName" th:value="${car.name}">
Controllerで次のようにModelへ値を入れていたとします。
@GetMapping("/cars/edit")
public String edit(Model model) {
CarDto car = new CarDto();
car.setCarId(1);
car.setName("プリウス");
car.setPrice(2500000);
model.addAttribute("car", car);
return "cars/edit";
}
ThymeleafがHTMLを作ると、ブラウザには次のようなHTMLが届きます。
<input type="text" name="carName" value="プリウス">
ユーザーがフォームを送信すると、次のようなリクエストパラメータとして送られます。
carName=プリウス
ここで大事なのは、th:valueではなく、最終的なHTMLのnameとvalueが送信されるという点です。
| 要素 | 役割 |
|---|---|
| th:value | HTMLのvalue属性に値を埋め込む |
| value | フォームで送られる値の候補になる |
| name | サーバー側で受け取るパラメータ名になる |
th:valueだけでは送信されない
よくある勘違いが、「th:valueを書けばサーバーに送られる」と考えてしまうことです。
実際には、name属性がないinputは、通常のフォーム送信でサーバー側のパラメータとして扱いにくくなります。
MDNのform要素の説明でも、フォームはWebサーバーへ情報を送信するための対話型コントロールを含む区画であり、例でもinputにname属性が付けられています。GETではフォームデータがURLに追加され、POSTではリクエスト本体として送信されます。
悪い例です。
<input type="text" th:value="${car.name}">このinputにはnameがありません。
画面には「プリウス」と表示されるかもしれません。
しかし、フォーム送信時にControllerでcarNameとして受け取ることはできません。
良い例です。
<input type="text" name="carName" th:value="${car.name}">Controllerでは次のように受け取れます。
@PostMapping("/cars/update")
public String update(@RequestParam String carName) {
System.out.println("送信された車名: " + carName);
return "redirect:/cars";
}
th:valueは値を入れるだけ。
nameが送信時の名前になる。
まず、この2つをしっかり分けてください。
th:valueで送ることができる情報
th:valueで送ることができる代表的な情報を整理します。
| 送れる情報 | 例 | 注意点 |
|---|---|---|
| 文字列 | 車名、ユーザー名、キーワード | 空文字や文字数に注意する |
| 数値 | ID、価格、数量 | Controller側の型変換に注意する |
| 日付文字列 | 2026-06-16 | 形式を揃える必要がある |
| 選択肢の値 | カテゴリID、ステータス値 | 表示名ではなくIDを送ることが多い |
| hidden値 | 更新対象のcarId | 改ざんされる前提で扱う |
| ボタンの値 | 削除する行番号、押された操作種別 | nameとvalueの組み合わせで判定する |
つまり、th:valueで送れるのは、最終的にHTMLのvalue属性に入る「文字列として表現できる値」です。
Javaのオブジェクトそのものを丸ごと安全に送るものではありません。
文字列を送る例
もっとも基本的なのは、文字列を送る例です。
<form th:action="@{/cars/search}" method="get">
<input type="text" name="keyword" th:value="${keyword}">
<button type="submit">検索</button>
</form>
Controllerです。
@GetMapping("/cars/search")
public String search(
@RequestParam(required = false) String keyword,
Model model) {
System.out.println("検索キーワード: " + keyword);
model.addAttribute("keyword", keyword);
return "cars/search";
}
この例では、検索キーワードを送れます。
検索画面で入力した値を、検索後も入力欄に残したいときにもth:valueは便利です。
たとえば、「プリウス」で検索したあとも、検索欄に「プリウス」と残っている画面ですね。
数値を送る例
数値も送れます。
<form th:action="@{/cars/detail}" method="get">
<input type="hidden" name="carId" th:value="${car.carId}">
<button type="submit">詳細を見る</button>
</form>
Controllerです。
@GetMapping("/cars/detail")
public String detail(@RequestParam int carId, Model model) {
CarDto car = carsDao.findById(carId);
model.addAttribute("car", car);
return "cars/detail";
}
この場合、carId=1のように送信されます。
ただし、HTTPリクエストとして送られる値は基本的に文字列です。
Spring Bootが@RequestParam int carIdを見て、文字列の"1"をint型へ変換してくれます。
もしcarId=abcのような値が送られると、intに変換できず400エラーになる可能性があります。
hiddenでIDを送る例
更新画面では、hiddenでIDを送ることがよくあります。
<form th:action="@{/cars/update}" method="post">
<input type="hidden" name="carId" th:value="${car.carId}">
<div>
<label>車名</label>
<input type="text" name="name" th:value="${car.name}">
</div>
<div>
<label>価格</label>
<input type="number" name="price" th:value="${car.price}">
</div>
<button type="submit">更新</button>
</form>
Controllerです。
@PostMapping("/cars/update")
public String update(
@RequestParam int carId,
@RequestParam String name,
@RequestParam int price) {
carsDao.update(carId, name, price);
return "redirect:/cars";
}
この例では、carId、name、priceを送れます。
hiddenは画面には見えません。
しかし、ブラウザの開発者ツールを使えば見えますし、値を書き換えることもできます。
つまり、hiddenで送った値は秘密ではありません。
ここは非常に大切です!
hiddenで送ってよい情報・危険な情報
| 情報 | hiddenで送ってよいか | 理由 |
|---|---|---|
| 更新対象のID | 送ることは多い | ただし、権限チェックが必須 |
| 検索条件 | 送ってよい | ユーザーが操作する条件だから |
| ページ番号 | 送ってよい | 一覧操作に必要だから |
| 価格 | 注意が必要 | 改ざんされると金額不正になる |
| ユーザー権限 | 送ってはいけない | 一般ユーザーが管理者に改ざんできる危険がある |
| ログインユーザーID | 基本は送らない | セッションから取得すべき |
| パスワード | 送ってはいけない | 画面や通信上の扱いに注意が必要 |
hiddenは「見えない入力欄」であって、「安全な金庫」ではありません。
たとえるなら、机の引き出しに紙を入れているだけです。
鍵付き金庫ではありません。
そのため、価格、権限、ログインユーザーIDのような重要情報は、hiddenの値を信用せず、サーバー側のDBやセッションで確認しましょう。
チェックボックスで送る情報
チェックボックスでもth:valueを使います。
<form th:action="@{/cars/filter}" method="get">
<label>
<input type="checkbox" name="colors" th:value="'red'">
赤
</label>
<label>
<input type="checkbox" name="colors" th:value="'blue'">
青
</label>
<button type="submit">検索</button>
</form>
赤と青の両方をチェックすると、次のように送られます。
colors=red&colors=blue
Controllerでは配列やListで受け取れます。
@GetMapping("/cars/filter")
public String filter(@RequestParam List<String> colors) {
System.out.println(colors);
return "cars/list";
}
Thymeleaf公式ドキュメントでも、booleanではない複数値のチェックボックスでは、th:fieldとth:valueを組み合わせる例が示されています。チェックされていないチェックボックス値はブラウザから送られないため、Thymeleafは問題を避けるためのhidden inputも自動的に追加すると説明されています。:contentReference[oaicite:2]{index=2}
チェックボックスで大切なのは、未チェックの項目は基本的に送られないという点です。
「falseが送られる」と思い込むと、バグの原因になります。
ラジオボタンで送る情報
ラジオボタンは、複数の選択肢から1つを選ぶときに使います。
<form th:action="@{/cars/search}" method="get">
<label>
<input type="radio" name="status" th:value="'ACTIVE'">
販売中
</label>
<label>
<input type="radio" name="status" th:value="'SOLD'">
売約済み
</label>
<button type="submit">検索</button>
</form>
販売中を選ぶと、次のように送られます。
status=ACTIVE
Thymeleaf公式ドキュメントでも、ラジオボタンでth:fieldとth:valueを組み合わせる例が示されています。:contentReference[oaicite:3]{index=3}
ラジオボタンでは、表示文字そのものではなく、サーバー側で扱いやすいコードをvalueにすることが多いです。
| 画面表示 | 送る値 |
|---|---|
| 販売中 | ACTIVE |
| 売約済み | SOLD |
| 販売停止 | STOPPED |
画面に見せる言葉と、サーバーに送る値は分けて考えましょう。
selectのoptionで送る情報
selectでは、optionのth:valueが重要です。
<select name="categoryId">
<option th:each="category : ${categoryList}"
th:value="${category.categoryId}"
th:text="${category.categoryName}">
</option>
</select>
たとえば、categoryNameが「SUV」、categoryIdが3なら、ブラウザには次のようなHTMLができます。
<option value="3">SUV</option>
ユーザーには「SUV」と見えます。
でも、サーバーへはcategoryId=3が送られます。
Thymeleaf公式ドキュメントでも、selectではselectタグにth:fieldを付け、optionタグのth:valueが現在選択されている項目を判断するために重要だと説明されています。:contentReference[oaicite:4]{index=4}
新人エンジニアは、selectでは「表示する名前」と「送るID」を分けて考えると理解しやすいです。
レストランのメニューでたとえるなら、お客さんには「カレー」と表示します。
厨房には「商品番号101」と送ります。
名前よりIDのほうが、プログラムでは扱いやすいからです。
ボタンのvalueを送る例
buttonにもth:valueを使えます。
<form th:action="@{/rows/remove}" method="post">
<button type="submit" name="removeIndex" th:value="${rowStat.index}">
削除
</button>
</form>
このボタンを押すと、removeIndex=0のように送られます。
Thymeleaf公式ドキュメントにも、動的な行を削除する例として、buttonのnameとth:valueを使い、rowStat.indexを送る例が示されています。:contentReference[oaicite:5]{index=5}
複数の削除ボタンがある一覧で、「どの行の削除ボタンが押されたのか」をサーバーへ伝えるときに便利です。
th:valueで送ることができない情報
次に、th:valueで送ることができない、または送るべきではない情報を見ていきましょう。
| 情報 | なぜ送れない・送るべきでないか |
|---|---|
| Javaオブジェクトそのもの | HTMLのvalueは文字列なので、オブジェクト構造はそのまま送れない |
| ListやMapそのもの | 複雑な構造はそのまま安全に復元できない |
| ログインユーザーの権限 | 改ざんされると危険 |
| 価格や割引率の確定値 | ユーザーが書き換えられるため、DBで再確認すべき |
| パスワードや秘密情報 | HTMLに出すべきではない |
| disabledの入力欄の値 | disabledのフォーム部品は送信されない |
| 表示専用のth:text | 画面に表示するだけで、フォーム値としては送られない |
ここからが実務でとても重要です。
th:valueで画面に埋め込んだ値は、ユーザーが見たり、変更したりできる前提で考えます。
「hiddenだから大丈夫」「画面に表示していないから大丈夫」は危険です。
Javaオブジェクトそのものは送れない
次のようなコードを考えます。
<input type="hidden" name="car" th:value="${car}">
この書き方をすると、carオブジェクトそのものがJavaのオブジェクトとして送られるわけではありません。
多くの場合、toString()された文字列のような形になります。
たとえば、次のような値です。
CarDto{carId=1, name='プリウス', price=2500000}
この文字列を送っても、Spring Bootが自動的に完全なCarDtoへ安全に戻してくれるわけではありません。
送るなら、必要な値だけに分けます。
<input type="hidden" name="carId" th:value="${car.carId}">
<input type="text" name="name" th:value="${car.name}">
<input type="number" name="price" th:value="${car.price}">
オブジェクト丸ごとではなく、必要なプロパティだけを送る。
この考え方が大切です。
ListやMapをそのまま送るのは向いていない
次のような書き方も避けたほうがよいです。
<input type="hidden" name="carList" th:value="${carList}">
carListがList<CarDto>だとしても、HTMLのvalueには文字列しか入りません。
一覧全体を丸ごと送るのではなく、ユーザーが選んだIDだけ送るのが基本です。
<select name="carId">
<option th:each="car : ${carList}"
th:value="${car.carId}"
th:text="${car.name}">
</option>
</select>
このようにすれば、選択されたcarIdだけを送れます。
サーバー側では、そのcarIdを使ってDBから最新の車情報を取得します。
@PostMapping("/cars/select")
public String select(@RequestParam int carId, Model model) {
CarDto car = carsDao.findById(carId);
model.addAttribute("car", car);
return "cars/detail";
}
大きな荷物を丸ごと送り返すのではなく、荷物番号だけ送るイメージです。
荷物番号を受け取ったサーバーが、倉庫であるDBから正しい荷物を取り出します。
権限情報はth:valueで送らない
次のようなhiddenは危険です。
<input type="hidden" name="role" th:value="${loginUser.role}">
一見すると、ログインユーザーの権限を送っているだけに見えます。
しかし、ユーザーはブラウザの開発者ツールで値を書き換えられます。
<input type="hidden" name="role" value="ADMIN">
もしサーバー側がこのroleを信じてしまうと、一般ユーザーが管理者扱いになる危険があります。
権限は、リクエストパラメータではなく、セッションや認証情報から取得すべきです。
@PostMapping("/admin/delete")
public String delete(HttpSession session) {
LoginUser loginUser =
(LoginUser) session.getAttribute("loginUser");
if (!"ADMIN".equals(loginUser.getRole())) {
return "redirect:/error/403";
}
return "redirect:/admin";
}
th:valueで送られてきたroleは信用してはいけません。
権限はサーバー側で確認しましょう。
価格や金額も信用してはいけない
ECサイトや注文システムでは、価格をhiddenで送る設計に注意が必要です。
危険な例です。
<input type="hidden" name="itemId" th:value="${item.itemId}">
<input type="hidden" name="price" th:value="${item.price}">
ユーザーがpriceを書き換えると、10000円の商品を100円として送れるかもしれません。
サーバー側がそのpriceを信用すると、不正注文につながります。
安全な考え方は、itemIdだけを送り、価格はDBから再取得する方法です。
@PostMapping("/order")
public String order(@RequestParam int itemId) {
ItemDto item = itemDao.findById(itemId);
int price = item.getPrice();
orderDao.insert(itemId, price);
return "redirect:/order/complete";
}
ユーザーから送られた金額ではなく、サーバー側のDBにある金額を正とします。
お店でたとえるなら、お客さんが「この商品は100円です」と紙に書いて持ってきても、レジでは商品マスタの価格を見ますよね。
それと同じです。
パスワードや秘密情報はth:valueに入れない
次のような書き方は避けてください。
<input type="hidden" name="password" th:value="${user.password}">
また、パスワード入力欄に既存パスワードを表示するのも危険です。
<input type="password" name="password" th:value="${user.password}">
パスワード、秘密トークン、APIキー、個人番号のような情報は、HTMLに埋め込むべきではありません。
HTMLに出た時点で、ブラウザ側に渡っています。
hiddenでも、開発者ツールで見えます。
秘密情報はth:valueに入れない。
これは必ず守ってください!
disabledの値は送信されない
画面で入力欄を無効化するためにdisabledを使うことがあります。
<input type="text" name="carName" th:value="${car.name}" disabled>
この入力欄は画面には表示されます。
しかし、disabledのフォームコントロールは送信されません。
MDNでも、disabled属性があるフォームコントロールは変更不可、フォーカス不可、フォーム送信不可になると説明されています。:contentReference[oaicite:6]{index=6}
つまり、次のようなコードで受け取ろうとしても、carNameが送られてこない可能性があります。
@PostMapping("/cars/update")
public String update(@RequestParam String carName) {
return "redirect:/cars";
}
表示だけ無効にしたいが値も送りたい場合は、readonlyを使う方法や、hiddenを別に用意する方法があります。
<input type="text" th:value="${car.name}" readonly>
<input type="hidden" name="carName" th:value="${car.name}">
ただし、hidden値は改ざん可能なので、重要な値ならDBで再確認しましょう。
th:textで表示しただけでは送信されない
次のように画面へ表示しているだけでは、フォーム送信時に値は送られません。
<p th:text="${car.name}"></p>
これは画面表示です。
送信値ではありません。
送信したいなら、input、select、textareaなどのフォーム部品にnameを付ける必要があります。
<p th:text="${car.name}"></p>
<input type="hidden" name="carName" th:value="${car.name}">
ただし、何度も言うように、重要な値はhiddenで送ったものを信用しないでください。
th:valueとth:fieldの違い
Spring BootとThymeleafのフォームでは、th:valueよりth:fieldを使う場面も多いです。
| 属性 | 役割 | 向いている場面 |
|---|---|---|
| th:value | value属性に値を入れる | hidden、button、option、単純な値設定 |
| th:field | Formオブジェクトのプロパティとフォーム部品を結びつける | 入力フォーム、バリデーション、再表示 |
Thymeleaf公式ドキュメントでは、th:fieldはフォームバッキングBeanのプロパティと入力欄をバインドする重要な機能であり、input、select、textareaなどに応じて動作すると説明されています。th:fieldの値は選択変数式、つまり*{...}で指定する必要があります。:contentReference[oaicite:7]{index=7}
Formクラスを使うなら、次のようにth:fieldを使うほうが自然です。
<form th:action="@{/cars/register}" th:object="${carForm}" method="post">
<input type="text" th:field="*{name}">
<input type="number" th:field="*{price}">
<button type="submit">登録</button>
</form>
この場合、th:fieldはid、name、valueを適切に生成してくれます。
一方で、hiddenでIDを送るだけならth:valueでも十分な場面があります。
<input type="hidden" name="carId" th:value="${car.carId}">
th:valueで送れるかどうかの判断基準
迷ったときは、次の表で判断してください。
| 質問 | YESなら | NOなら |
|---|---|---|
| 文字列として表せる値か | th:valueに入れられる | そのまま送るのは難しい |
| ユーザーに見られてもよいか | 送ってもよい候補 | 送らない |
| 書き換えられてもサーバー側で検証できるか | 送ってもよい候補 | 送らない |
| DBから再取得できるか | IDだけ送るのがよい | 設計を見直す |
| Form入力として扱う値か | th:fieldも検討する | th:valueでよい場合もある |
特に重要なのは、「ユーザーに書き換えられても大丈夫か」です。
ブラウザから送られる値は、すべて改ざんされる可能性があります。
画面上の入力値も、hidden値も、select値も、radio値も、サーバー側では信用しすぎないでください。
実務でよくある正しい使い方
実務でよくある安全な使い方をまとめます。
| 場面 | 送る値 | サーバー側でやること |
|---|---|---|
| 詳細画面へ移動 | carId | DBから車情報を再取得する |
| 検索 | keyword、minPrice、maxPrice | 検索条件として使う |
| 更新 | carId、name、price | 権限確認後に更新する |
| 削除 | carId | ログインユーザーが削除可能か確認する |
| カテゴリ選択 | categoryId | 存在するカテゴリかDBで確認する |
IDを送る。
サーバー側でDBから最新情報を取り直す。
権限や整合性を確認する。
この流れが安全です。
まとめ
Thymeleafのth:valueは、HTMLのvalue属性へJava側の値を埋め込むための属性です。
ただし、th:valueを書いただけでサーバーへ送られるわけではありません。
フォーム送信では、最終的にブラウザに生成されたHTMLのnameとvalueが重要になります。
| 分類 | 内容 |
|---|---|
| 送ることができる情報 | 文字列、数値、ID、検索条件、選択肢の値、ボタンの値 |
| 送ることができない情報 | Javaオブジェクトそのもの、ListやMapそのもの |
| 送るべきではない情報 | パスワード、秘密情報、権限、信用すべき価格、ログインユーザーID |
| 注意が必要な情報 | hidden値、disabledの値、画面表示だけのth:text |
一言でまとめるなら、th:valueは「送信値の中身をHTMLに埋め込む道具」です。
しかし、送信されるにはname属性が必要であり、送られてきた値はサーバー側で必ず検証する必要があります。
新人エンジニアは、まず次の3つを覚えてください。
| 覚えること | 理由 |
|---|---|
| th:valueだけでは送れない | name属性がないとパラメータとして受け取りにくい |
| hiddenは秘密ではない | ユーザーが値を書き換えられるため |
| 重要情報はDBやセッションで確認する | リクエスト値を信用しすぎると危険なため |
今後の学習では、th:value、name属性、@RequestParam、@ModelAttribute、th:field、hidden、disabled、select、checkbox、radioを順番に確認してください。特に、画面から送られる値は「ユーザーが変更できる値」だと考える習慣を持ちましょう!
セイ・コンサルティング・グループでは新人エンジニア研修のアシスタント講師を募集しています。
投稿者プロフィール


