前回は「Controllerの役割」について学びました。Controllerは、ブラウザからのリクエストを受け取り、適切な処理を実行した上でレスポンスを返すという、重要な役割を担っています。

今回は、入力フォームから送信されたデータを Controller で受け取る方法を説明します。

今回の学習の重点をオレンジの枠線で図示します。

ブラウザのフォームからControllerにデータを渡すところを重点的に学びます

1. フォームの復習

1.1 プロジェクト構成イメージ

src/main/resources/templates 配下に、Thymeleafのテンプレートファイル(index.htmlやans.htmlなど)を配置

src/main/java/com.example.demo.controller などのパッケージにControllerクラスを配置

以下のような構成を例示します。

my-demo-form
 ┣ src
 ┃ ┣ main
 ┃ ┃ ┣ java
 ┃ ┃ ┃ ┣ com.example.demo
 ┃ ┃ ┃ ┃ ┗ controller
 ┃ ┃ ┃ ┃    ┣ AddController.java    (Controller)
 ┃ ┃ ┃ ┃ ┗ DemoApplication.java     (Spring Boot起動クラス)
 ┃ ┃ ┗ resources
 ┃ ┃    ┗ templates
 ┃ ┃       ┣ index.html            (入力フォーム)
 ┃ ┃       ┗ ans.html              (結果表示用)
 ┗ (以下省略)

1.2 フォーム画面の例

Thymeleafを使ったindex.html を用意します。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>index.html</title>
</head>
<body>
  <form th:action="@{/add}" method="get">
    <input type="text" name="num1"><br>
    <input type="text" name="num2"><br>
    <button type="submit">送信</button>
  </form>
</body>
</html>

質問(HTMLフォームに関して)

  • ①このフォームのHTTPメソッドは何ですか?
あなたの答え:
  • ②このHTTPメソッドの指定は省略できますか?
あなたの答え:
  • ③フォームの中の部品はいくつありますか?
あなたの答え:
  1. ④それぞれどのような部品でしょうか?
あなたの答え:
  • ⑤「name="○○"」で付けられた名前はControllerでどのように使われますか?
あなたの答え:

このname属性に設定された名前はリクエストパラメータとして、文字列形式でControllerに送られます。

解説:

  • フォーム送信で渡されるデータは「name属性の値」と「ユーザーの入力内容」がセットになって送られます。
  • Spring BootのController側では @RequestParam("num1") String num1 のように受け取れます。
  • すべて文字列として渡されるため、数値として使う場合は Integer.parseInt() などで変換が必要です。

Thymeleafでは、th:action="@{/add}" のように書くと、Spring Bootで @GetMapping("/add") を指定したControllerメソッドが呼び出されます。

HTMLのフォームでは、以下のように action 属性で送信先URLを文字列として指定しました。

<form action="/add" method="post">

Thymeleafを使用する場合、以下のように th:action を使って書きます。

<form th:action="@{/add}" method="post">

なぜなら以下のような違いがあるからです。

項目action="/add"(通常)th:action="@{/add}"(Thymeleaf)
コンテキストパス(≒アプリケーション名)が変わったら手動対応が必要自動対応
保守性低い(修正が大変)高い(変更に強い)

フォームは「何を、どのメソッドで、どのプログラムに送るのか」を書くものだと理解しましょう。上記の例でいえば、「num1,num2という名前のつけられた1行テキストに入力された文字列を、GETメソッドで、 @GetMapping("/add") を指定したControllerに送る」ということが書かれているのですね。

2箇所合わせる必要がある

2. Controllerの役割

2.1 データの受取方法

フォームから送ったデータを受け取るには以下のような 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 AddController {

    @GetMapping("/")
    public String goIndex() {
        return "index";
    }
    
    @GetMapping("/add")
    public String doGetAdd(
    		@RequestParam(name="num1") String num1Str,
    		@RequestParam(name="num2") String num2Str,
    		Model model
    		) {
    	// nullチェックや例外処理は未実装
    	int num1 = Integer.parseInt(num1Str);
    	int num2 = Integer.parseInt(num2Str);
    	
    	model.addAttribute("ans", num1 + num2);
    	
    	return "ans";
    }
}

質問(ControllerのdoGetAddメソッドに関して)

  • ①index.htmlはどのようなURLパスで呼び出されますか?
あなたの答え:
  • ②リクエストパラメータにはどの様な値がどの様な名前で格納されていますか?
あなたの答え:
  • ③Modelにはどのような値を、どのような名前で格納していますか?
あなたの答え:
  • ④答えを表示しているHTMLテンプレートのファイル名は何ですか?
あなたの答え:
  • ⑤ブラウザのアドレスバー表記は、フォームに「1」と「2」をそれぞれ入力して送信した場合どうなりますか?
あなたの答え:http://localhost:8080/

例題

上記、index.htmlで「name="num1"」となっているところを「name="number1"」と書き換えたとします。そうすると他にどこをどのように書き換える必要がありますか?

あなたの答え:

例題

上記、index.htmlからControllerに対して確実にデータが送られていることを①printデバッグ、②デバッガを使ったデバッグで確認しなさい。

確かめた結果:

また、デバッガで"ans"の値は見られないことも確認してください。

2.2 @RequestParamの役割

2章の数当てゲームでは、「@RequestParam(name="num1") String num1Str,」を「String num1」のように@RequestParam を省略して書いていました。

@RequestParam を省略する場合、リクエストのクエリパラメータのキー(例: num1)と、Controllerのメソッド引数名 (num1) が完全に一致している必要があります。
例えば、クエリパラメータとして ?num1=10 のように送信される場合、メソッド引数も num1 であれば、自動的に値が結びつけられます。

ただし、@RequestParamを使用することには、以下のようなメリットがあります。

必須 (required=true / false)

デフォルトでは @RequestParam は 必須 ですが、required=false にすると省略可能になります。

例:

@GetMapping("/hello")
public String hello(
      @RequestParam(required = false) String name) {
        return name != null ? "Hello, " + 
    		name : "Hello, Guest";
}

GET /hello?name=John → "Hello, John"

GET /hello → "Hello, Guest"(エラーにならない)

デフォルト値 (defaultValue)

値が渡されなかったときにデフォルト値を指定できます。

例:

    @GetMapping("/hello")
    public String hello(@RequestParam
    		(defaultValue = "Guest") String name) {
        return "Hello, " + name;
    }

GET /hello?name=John → "Hello, John"

GET /hello → "Hello, Guest"(エラーにならない)

ご提示いただいた「必須設定」「デフォルト値」に加えて、@RequestParam を使うことで得られる3つの大きなメリット(機能)を書き加えました。

既存のドキュメントにそのまま組み込めるフォーマットにしています。

パラメータ名のカスタマイズ (name / value)

URLのリクエストパラメータ名と、Javaプログラム内の変数名を違うものにしたい場合に、明示的に名前を指定してマッピングできます。

URLにはスネークケース(user_name)やケバブケース(user-name)を使い、Java側ではキャメルケース(userName)を使いたいときなどに便利です。

例:

@GetMapping("/search")
public String search(@RequestParam(name = "search_word") String word) {
    return "検索ワード: " + word;
}

複数値やまとめての一括受け取り (List)

同じ名前のパラメータが複数送られてくる場合(チェックボックスの選択値など)は、自動的に List や配列として受け取ることができます。

例(複数値の受け取り):

@GetMapping("/hobby")
public String getHobbies(@RequestParam List<String> tags) {
    return "選択された趣味: " + tags;
}

  • GET /hobby?tags=sports&tags=music → "選択された趣味: [sports, music]"
  • GET /hobby?tags=sports,music (カンマ区切りでも自動でリストに分解してくれます)

例(Mapで一括受け取り):

@GetMapping("/filter")
public String filter(@RequestParam Map<String, String> allParams) {
    return "全パラメータ: " + allParams;
}

  • GET /filter?category=books&sort=price&page=2 → "全パラメータ: {category=books, sort=price, page=2}"

調べてみましょう

Modelをデバッガで見るとどのように見えますか?

2.2 結果表示用のThymeleafテンプレート

先ほどmodel.addAttribute("ans", ~)で渡した値をThymeleafで取り出すには、${ans} と書きます。

以下のPタグの要素の内容「ここに計算結果が表示されます」は値と置き換わります。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>ans.html</title>
</head>
<body>
  <p th:text="${ans}">ここに計算結果が表示されます</p>
</body>
</html>

これで、index.html で入力 → /add にGET送信 → AddController で計算 → ans.html に結果表示、という流れをSpring Bootで実現できました。

3. HTTPリクエストの読み方

ブラウザのアドレスバーに表示されるURL例:

  1. http:// ・・・通信プロトコル
  2. localhost ・・・サーバー名(ホスト名)
  3. :8080 ・・・ポート番号
  4. /add ・・・ControllerのURLパス
  5. ?num1=1&num2=2 ・・・GETリクエストで送るパラメータ。複数ある場合は & で連結

4. @RequestParam と 文字列の変換

今回の注目点はこの部分です。

@RequestParam("num1")

アノテーションを使っています。

このアノテーションはフォームから送られたリクエストに入っている文字列を取り出すことができます。ここでフォームから送られるデータはすべて文字列として送られることを思い出してください。

また、フォームから送信されるデータは1つとは限りません。そのため、“num1”のようにテキストフィールドに設定したname属性の値によって、データを区別して受け取る必要があります。

今回の処理の概要を下図にまとめておきます。

それぞれの①~④が対応していることに着目

@RequestParamは フォームから送られた文字列を受け取ります。

後で数値として利用するなら、Integer.parseInt()などで変換が必要です。

int num1 = Integer.parseInt(num1Str);

なお、NumberFormatException が発生する可能性があるため、エラー処理は必要です。

5. エラー入力を適切に処理する

入力が想定外のとき

ビジネスロジック(Modelクラス)やControllerにて、検証処理(バリデーション)を行います。

    @GetMapping("/add")
    public String doGetAdd(
         @RequestParam(name="num1") String num1Str,
         @RequestParam(name="num2") String num2Str,
         Model model
    ) {
        try {
            int num1 = Integer.parseInt(num1Str);
            int num2 = Integer.parseInt(num2Str);
            model.addAttribute("ans", num1 + num2);
        } catch (NumberFormatException e) {
            model.addAttribute("error", "整数を入力。");
            return "error";
        }

        return "ans";
    }
}

このように、異常な入力を受け取ったら適切なメッセージをセットし、エラー画面を返します。

6. ハイパーリンクやボタンを使ってGETメソッドでデータ送信

ここでは、天気予報プログラムのプロトタイプを考えてみましょう。

全国のユーザーが現在の天気を送るとリアルタイムで全国の天気が分かり、また、予測も可能になるというシステムのプロトタイプです。

天気予報サイトのイメージ

6.1 Controller

以下のようなControllerを用意します。ちなみに慣れてくるとControllerを見ればおおよそどのようなことをするWebアプリケーションかという見当がつくようになります。

@Controller
public class WeatherController {

	@GetMapping("/link")
	public String link() {
		return "link"; // link.html
	}

	@GetMapping("/button")
	public String button() {
		return "button"; // button.html
	}

	@GetMapping("/weather")
	public String weather2(String weather, Model model) {
		model.addAttribute("weather", weather);
		return "weather"; // weather.html
	}
}

6.2 リンクでパラメータを送る

リンクを使ってデータを送ることができます。以下のように書けば、

@{/URL(リクエストパラメータ名='値')}

実際に生成されるHTMLは以下のようになります。

"/URL?リクエストパラメータ名=値"

以下が具体例です。

<a th:href="@{/weather(weather='晴れ')}">晴れ</a>
<a th:href="@{/weather(weather='曇り')}">曇り</a>
<a th:href="@{/weather(weather='雨')}">雨</a>

  • @{} を使うと、 URL を自動生成できるのでしたね。
  • クエリパラメータ(weather=〇〇)は (key='value') の形で記述する。valueがシングルクォーテーション(')で囲まれているのはダブルクオーテーション(")で囲うとHTMLの href 属性のダブルクォーテーションとぶつかり、構文エラーになるからです。
ThymeleafとHTMLの対比

aタグを使ったリンクは常にGETメソッドになることを覚えておいてください。

元来、HTTPのGETメソッドは主にWebページや他のリソース(例えば、画像やCSSファイル)を取得するために使用されてきました。

ブラウザがリンクをクリックするか、URLを直接入力すると、ブラウザはそのURLに対応するリソースを取得するためにGETリクエストをサーバーに送信するのです。

GETはデータをURLのクエリパラメータとして送信する方法、POSTはリクエストの本文にデータを送信する方法です。フォームの送信でよく使われますが、GETを指定した場合はURLにパラメータが付き、POSTの場合はURLには付きません。(注:この研修の範囲外ですが、フォーム以外の場面でもGETやPOSTは使われます。)

6.3 weather.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>weather.html</title>
</head>
<body>
  今は <span th:text="${weather}"></span> なんですね。
</body>
</html>

例題

上記link.htmlに雪の場合を加えなさい。

例題

これまで作成してきた数当てゲームをリンクを使って実現しなさい。ただし、再利用できるファイルはできるだけ再利用すること。

画面イメージ

6.4 ボタンを使う

テキストボックスからリンクに入力方法を変えたことでユーザーの使いやすさが向上しました。さらにフォーム側での入力チェック(バリデーション)も不要になりました。

ここでは、さらにユーザー入力がしやすいようにボタンを使ってデータを送ります。ボタンはスマートフォンでも押しやすいですからね。HTMLで学んだようにボタンはフォームタグで囲む必要がありましたね

<form th:action="@{/weather}" method="get">
    <button type="submit" name="weather" value="晴れ">晴れ</button>
    <button type="submit" name="weather" value="曇り">曇り</button>
    <button type="submit" name="weather" value="雨">雨</button>
</form>

例題

上記button.htmlに雪の場合を加えなさい。

例題

数当てゲームをボタンを使って実現しなさい。

画面イメージ

サーバー側のバリデーションが省略できない理由

リンクやボタンを使ったデータ送信は、ユーザーがブラウザの開発者ツールなどを利用して容易にデータを改ざんできるため、クライアント側のバリデーションだけでは安全性が保証されません。悪意のある攻撃や誤ったデータ送信を防ぐためには、サーバー側でも必ずバリデーションを行い、データの整合性やシステムの安全性を保つ必要があります。なお、研修中はそこまでの配慮は不要とします。

ここまででフォームからデータを送る基本を学びました。テキストボックスに入力した値が「name=値」のペアとなってControllerに届き、@RequestParamで受け取れるのでしたね。

しかし、実際のWebアプリのフォームはテキストボックスだけではありません。ラジオボタン、チェックボックス、プルダウン、テキストエリア……。HTMLの学習で登場した様々なフォーム部品から、Spring BootのControllerへデータを送る方法を学びましょう。部品の見た目は違っても、送られる仕組みは前回とまったく同じです。そのことを確かめながら進めていきましょう。

どんなフォーム部品も、送られるのは「name=値」のペア

7. その他のフォーム部品

前回学んだとおり、フォームを送信すると、ブラウザは部品のname属性と値をペアにしてサーバーに送ります。

paymentMethod=loan&color=red&options=navi&options=etc

テキストボックスとの違いは、値をユーザーが入力するのか、開発者があらかじめvalue属性で用意しておくのか、という点だけです。ラジオボタンやチェックボックス、プルダウンでは、ユーザーは用意された選択肢から「選ぶ」だけ。実際に送られる値は、私たち開発者がvalue属性で決めておきます。

7.1 サンプルプロジェクトの構成

本記事では、自動車購入アンケートを題材にします。

my-demo-survey
┣ src
┃ ┣ main
┃ ┃ ┣ java
┃ ┃ ┃ ┣ com.example.demo
┃ ┃ ┃ ┃ ┗ controller
┃ ┃ ┃ ┃   ┣ SurveyController.java (Controller)
┃ ┃ ┃ ┃   ┗ DemoApplication.java (Spring Boot起動クラス)
┃ ┃ ┗ resources
┃ ┃   ┗ templates
┃ ┃     ┣ survey.html (アンケート入力フォーム)
┃ ┃     ┗ surveyResult.html (結果表示用)
┗ (以下省略)

survey.html(入力フォーム)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>自動車購入アンケート</title>
</head>
<body>
    <h2>自動車購入アンケート</h2>
    <form th:action="@{/survey}" method="post">

        <!-- ① ラジオボタン:同じname属性のグループから1つだけ選択 -->
        <p>お支払い方法:</p>
        <label><input type="radio" name="paymentMethod" value="cash" checked> 現金一括</label>
        <label><input type="radio" name="paymentMethod" value="loan"> ローン</label>

        <!-- ② プルダウン:選択肢はoptionタグのvalue属性で指定 -->
        <p>ご希望のボディカラー:</p>
        <select name="color">
            <option value="white">ホワイト</option>
            <option value="black">ブラック</option>
            <option value="red">レッド</option>
        </select>

        <!-- ③ チェックボックス:同じname属性で複数選択が可能 -->
        <p>ご希望のオプション(複数選択可):</p>
        <label><input type="checkbox" name="options" value="カーナビ"> カーナビ</label>
        <label><input type="checkbox" name="options" value="ETC"> ETC</label>
        <label><input type="checkbox" name="options" value="ドライブレコーダー"> ドライブレコーダー</label>

        <!-- ④ テキストエリア:複数行の文章を入力できる -->
        <p>ご要望があればお書きください:</p>
        <textarea name="comment" rows="4" cols="40"></textarea>

        <!-- ⑤ hidden:画面には表示されないが、送信はされる -->
        <input type="hidden" name="campaignCode" value="SPRING2026">

        <p><button type="submit">送信</button></p>
    </form>
</body>
</html>
  • ①ラジオボタンで「ローン」を選んだとき、サーバーに送られる「name=値」のペアはどうなりますか?(画面の表示文字「ローン」ではないことに注意)
あなたの答え:

このコードのポイント

ラジオボタン 〜 同じnameで1つだけ

<input type="radio" name="paymentMethod" value="cash" checked>
<input type="radio" name="paymentMethod" value="loan">
  1. 同じname属性を付けたラジオボタンは1つのグループになり、その中から1つだけ選択できます。逆に言うと、nameが違うとグループにならず、両方選べてしまいます。
  2. 送信される値はvalue属性の値です。画面に表示している「現金一括」「ローン」という文字は送られません。
  3. checkedを付けておくと初期選択になります。ラジオボタンは「未選択のまま送信」されると値そのものが送られないため、初期値を付けておくのが安全です。

プルダウン 〜 selectとoption

<select name="color">
    <option value="white">ホワイト</option>
</select>
  1. name属性はselectタグに付け、送信される値は選ばれたoptionタグのvalue属性です。
  2. タグで挟まれた「ホワイト」は画面に表示される文字であって、送信される値ではありません。ラジオボタンと同じく、表示と値は別物です。

チェックボックス 〜 同じnameで複数選択

<input type="checkbox" name="options" value="カーナビ">
<input type="checkbox" name="options" value="ETC">
  1. チェックボックスはラジオボタンと違い、同じnameでも複数チェックできます
  2. 例えばカーナビとETCにチェックして送信すると、options=カーナビ&options=ETCのように、同じ名前のペアが複数送られます。Controller側でどう受け取るかは後ほど見ていきます。

テキストエリア 〜 複数行の入力

textareaは開始タグと終了タグの間が入力欄になります。value属性は使いません。改行を含む長い文章もそのまま送信できます。

hidden 〜 見えないけれど送られる

type="hidden"の部品は画面に表示されませんが、送信時には他の部品と同じように「name=値」のペアとして送られます。画面をまたいでデータを引き継ぎたいときなどに使います。

ただし注意があります。「見えない=ユーザーが触れない」ではありません。ブラウザの開発者ツール(F12)を使えば、hiddenの値は誰でも見られますし、書き換えることもできます。hiddenの値を信用してはいけないということは、セキュリティの観点からとても重要なので覚えておいてください。

調べてみましょう 開発者ツールで送信データを見てみよう

百聞は一見にしかず。F12で開発者ツールを開き、「ネットワーク」タブを表示した状態でフォームを送信してみましょう。リクエストの中に、今回学んだ「name=値」のペアがそのまま並んでいるのが確認できます。どんなに複雑なフォームでも、送られているのは結局このペアの集まりです。

SurveyController.java(コントローラー)

package com.example.demo.controller;

import java.util.List;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class SurveyController {

    /**
     * アンケート入力フォームを表示するメソッド。
     */
    @GetMapping("/survey")
    public String showForm() {
        return "survey";
    }

    /**
     * アンケートの送信内容を受け取り、結果画面を表示するメソッド。
     * どの部品から送られた値も @RequestParam で受け取れる。
     */
    @PostMapping("/survey")
    public String submit(
            @RequestParam("paymentMethod") String paymentMethod,
            @RequestParam("color") String color,
            @RequestParam(name = "options", required = false) List<String> options,
            @RequestParam(name = "comment", defaultValue = "") String comment,
            @RequestParam("campaignCode") String campaignCode,
            Model model) {

        model.addAttribute("paymentMethod", paymentMethod);
        model.addAttribute("color", color);
        model.addAttribute("options", options);
        model.addAttribute("comment", comment);
        model.addAttribute("campaignCode", campaignCode);

        return "surveyResult";
    }
}

このコードのポイント

複数の値はListで受け取る

@RequestParam(name = "options", required = false) List<String> options
  1. チェックボックスのように同じnameのペアが複数送られてくる場合、List<String>型(またはString[]型)で受け取ると、チェックされた値が全部リストに入ります。
  2. JavaSEの学習で登場したコレクション(List)が、ここで活躍します。

required = false 〜 送られてこないかもしれない値

チェックボックスは、1つもチェックされていないとoptionsというペア自体が送信されません。何も指定していないと、Springは「必須のパラメータがない」と判断してエラー(400 Bad Request)にしてしまいます。

そこでrequired = falseを指定し、「送られてこなくてもよい」と伝えます。このときoptionsにはnullが入ります。

defaultValue 〜 空のときの初期値

@RequestParam(name = "comment", defaultValue = "") String comment

defaultValueを指定すると、値が送られてこなかったときにその値が使われます。なお、defaultValueを指定するとrequired = falseも自動的に適用されます。

  • ①オプションを1つもチェックせずに送信したとき、optionsの中身はどうなりますか?
あなたの答え:

surveyResult.html(結果表示)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>アンケート結果</title>
</head>
<body>
    <h2>ご回答ありがとうございました</h2>
    <p>お支払い方法: <span th:text="${paymentMethod}"></span></p>
    <p>ボディカラー: <span th:text="${color}"></span></p>
    <p>オプション: <span th:text="${options}"></span></p>
    <p>ご要望: <span th:text="${comment}"></span></p>
    <p>キャンペーンコード: <span th:text="${campaignCode}"></span></p>
</body>
</html>

${options}にListを渡すと、[カーナビ, ETC]のような形式で表示されます。1件ずつきれいに表示する方法(繰り返し処理)は、Thymeleafの章で学びます。

まとめ:部品と受け取り方の対応表

フォーム部品送られる値Controllerでの受け取り
テキストボックス入力された文字列String
ラジオボタン選択されたvalue属性の値(1つ)String
プルダウン選択されたoptionのvalue(1つ)String
チェックボックスチェックされたvalue(0個以上)List<String> + required = false
テキストエリア入力された文字列(改行含む)String
hiddenvalue属性の値String

第4章の今回は、フォームなどを使ってControllerにデータを渡し、Thymeleafで表示する方法について学びました。

第5章は「セッション管理を学ぶ 〜 ログイン機能と買い物かごを作る」です。

セッション管理を学ぶことで複数のページをまたがってデータを保持することができるようになります。

セッション管理を学ぶことで複数のページをまたがってデータを保持できる