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:valueHTMLの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:valuevalue属性に値を入れるhidden、button、option、単純な値設定
th:fieldFormオブジェクトのプロパティとフォーム部品を結びつける入力フォーム、バリデーション、再表示

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値も、サーバー側では信用しすぎないでください。

実務でよくある正しい使い方

実務でよくある安全な使い方をまとめます。

場面送る値サーバー側でやること
詳細画面へ移動carIdDBから車情報を再取得する
検索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を順番に確認してください。特に、画面から送られる値は「ユーザーが変更できる値」だと考える習慣を持ちましょう!

セイ・コンサルティング・グループでは新人エンジニア研修のアシスタント講師を募集しています。

投稿者プロフィール

山崎講師
山崎講師代表取締役
セイ・コンサルティング・グループ株式会社代表取締役。
岐阜県出身。
2000年創業、2004年会社設立。
IT企業向け人材育成研修歴業界歴20年以上。
すべての無駄を省いた費用対効果の高い「筋肉質」な研修を提供します!
この記事に間違い等ありましたらぜひお知らせください。

学生時代は趣味と実益を兼ねてリゾートバイトにいそしむ。長野県白馬村に始まり、志賀高原でのスキーインストラクター、沖縄石垣島、北海道トマム。高じてオーストラリアのゴールドコーストでツアーガイドなど。現在は野菜作りにはまっている。