Spring Bootの入力値をどこで受けるかを解説する記事のアイキャッチ。若手エンジニアがJava道場風の学習空間でControllerの引数を学ぶイラスト。

実装・エラー解決

Spring Bootの入力値はどこで受ける?@RequestParam・@PathVariable・@RequestBodyの使い分け

AIによる要約

Spring Bootの入力値は、URLの一部なら@PathVariable、クエリ文字列なら@RequestParam、JSONボディなら@RequestBody、HTMLフォームならFormクラスで受けるのが基本です。値がどこから来るかを見ると迷いにくくなります。

新人SE
新人SE
Controllerの引数にいろいろなアノテーションが出てきて、どれを使えばいいか分かりません。
ポンコツSE
ポンコツSE
まずは入力値がURL、クエリ、フォーム、JSONのどこにあるかを見ます。アノテーション名だけで覚えるより、HTTPリクエストのどの部分を読むかで整理しましょう。

Spring BootのControllerでは、リクエストから値を受け取る方法が複数あります。

利用者管理のControllerを例にします。10 が「どの利用者か」を表すときはパスに置き、tanaka が「一覧の絞り込み」を表すときはクエリに置きます。登録する内容は、APIならJSON、HTML画面ならフォームの本文として送ります。

この記事のポイント

  • URLの一部は@PathVariableで受ける
  • クエリ文字列や単発のフォーム値は@RequestParamで受ける
  • JSONのリクエストボディは@RequestBodyで受ける
  • HTMLフォームの複数項目はFormクラスで受ける

実行用サンプル

利用者管理を題材に、パス・クエリ・JSON本文・HTMLフォームを比較します。 ソース全文、JUnitの検証コード、curlの実行ファイル、pom.xml、READMEを収録しています。JDK 17とMaven 3.9系で mvn test を実行します。Spring Boot 3.5.16のサンプルです。

まず入力値がどこにあるかを見る

Springのアノテーションを暗記しようとすると混乱します。先に見るべきなのは、入力値がHTTPリクエストのどこに入っているかです。

  • パスのID。/api/users/10 の 10
  • クエリの検索語。/api/users?keyword=tanaka の keyword
  • HTMLフォーム: 画面の入力欄からPOSTされる値
  • JSONボディ: APIクライアントから送られるJSON

ブラウザーの開発者ツールで、Request URL、Query String Parameters、Request PayloadまたはForm Data を順に見ます。たとえば name がPayloadのJSONにあれば、このサンプルでは @RequestBody で受け取ります。

@PathVariableはURLの一部を受ける

@PathVariableは、URLのパスに含まれる値を受け取ります。詳細画面、編集画面、削除処理など、対象リソースをURLで表すときによく使います。

UserInputController.java の抜粋。パスの利用者IDを受ける

    @GetMapping("/api/users/{userId}")
    public ResponseEntity<UserView> detail(@PathVariable("userId") long userId) {
        return ResponseEntity.of(userService.find(userId));
    }

GET /api/users/10 はパスの 10 を long の userId として受け、利用者10を返します。/api/users/tanaka では long に変換できず、400になり詳細取得は呼ばれません。

@RequestParamはクエリ文字列や単発の値を受ける

@RequestParamは、URLの後ろにつくクエリ文字列や、単発のフォーム値を受け取るときに使います。

UserInputController.java の抜粋。クエリの検索語とページ番号を受ける

    @GetMapping("/api/users")
    @ResponseBody
    public List<UserView> search(@RequestParam(name = "keyword", defaultValue = "") String keyword,
                                 @RequestParam(name = "page", defaultValue = "1") int page) {
        return userService.search(keyword, page);
    }

GET /api/users?keyword=tanaka&page=1 は検索条件をクエリで渡し、Tanakaを含む一覧を返します。page を省略すると1です。page=abc は int に変換できず、検索に進みません。

@RequestBodyはJSONを受ける

@RequestBodyは、リクエストボディのJSONをJavaオブジェクトへ変換します。画面フォームではなく、REST APIでよく使われます。

UserInputController.java の抜粋。本文のJSONを受ける

    @PostMapping(path = "/api/users", consumes = APPLICATION_JSON_VALUE)
    public ResponseEntity<UserView> create(@Valid @RequestBody UserCreateRequest request) {
        UserView user = userService.create(request.name(), request.email(), request.notificationLimit());
        return ResponseEntity.created(URI.create("/api/users/" + user.id())).body(user);
    }

POST /api/users は下のJSONを本文から読みます。登録に成功すると201と Location が返り、そのURLから登録した利用者を取得できます。

create-user.json の全文

{
  "name": "Suzuki",
  "email": "suzuki@example.com",
  "notificationLimit": 5
}

HTMLフォームはFormクラスで受けることが多い

Thymeleafなどの画面フォームでは、Formクラスで受ける形がよく使われます。入力値が複数あり、エラー時に画面へ戻す必要があるからです。

UserInputController.java の抜粋。HTMLフォームの項目を受ける

    @PostMapping(path = "/users", consumes = APPLICATION_FORM_URLENCODED_VALUE)
    public String register(@Valid @ModelAttribute("registration") UserRegistrationForm form,
                           BindingResult errors) {
        if (errors.hasErrors()) {
            return "users/new";
        }
        userService.create(form.name(), form.email(), 10);
        return "redirect:/users/new";
    }

POST /users のフォーム本文では name=Sato&email=sato%40example.com のように値を送ります。@ModelAttribute が入力をFormへ割り当てます。形式が不正なメールアドレスは保存せず、入力とエラーを表示します。

使い分けを表で整理する

送る値HTTPリクエスト内の位置Controllerの受け口
利用者ID 10GET /api/users/10 のパス@PathVariable("userId")
検索語 tanaka とページ 1GET /api/users?keyword=tanaka&page=1 のクエリ@RequestParam
登録内容 name、email、notificationLimitPOST /api/users のJSON本文@RequestBody UserCreateRequest
氏名とメールアドレスPOST /users のURLエンコードされた本文@ModelAttribute UserRegistrationForm

サンプルを展開し mvn spring-boot:run で起動してから、別のターミナルで展開先へ移動して試します。上から順に、利用者詳細200、検索一覧200、API登録201、画面登録302を返します。

locations.sh の抜粋。入力の位置が異なる4リクエスト

base_url="${BASE_URL:-http://localhost:8080}"

# パスの10が利用者ID
curl -si "$base_url/api/users/10"

# クエリのkeywordとpageが検索条件
curl -si "$base_url/api/users?keyword=tanaka&page=1"

# 本文にJSONを送る
curl -si "$base_url/api/users" \
  -H 'Content-Type: application/json' \
  --data-binary @requests/create-user.json

# 本文にURLエンコードしたフォームの値を送る
curl -si "$base_url/users" \
  --data-urlencode 'name=Sato' \
  --data-urlencode 'email=sato@example.com'

現場でよくある間違い

注意点

  • JSONを@RequestParamで受けようとして値が入らない
  • /api/users/10 の 10 を、クエリを読む @RequestParam で探してしまう
  • 登録フォームを大量の@RequestParamで受けてしまう
  • @RequestBodyにHTMLフォームの送信値が入ると思い込む

この記事とあわせて読みたい

関連記事Spring Bootの@RequestParamとFormクラスの違いは?使い分けを解説 @RequestParam は単発の値を受け取るときに便利です。一方、Formクラスは画面入力や検索条件のように複数項目を1つの入力として扱いたいときに向いています。 記事を読む 関連記事Spring Bootの@RequestBodyとは?JSONを受け取る使い方と注意点 @RequestBody は、HTTPリクエストのボディに入っているJSONをJavaオブジェクトへ変換して受け取るために使います。クエリ文字列やHTMLフォームを受けるためのものではありません。 記事を読む 関連記事Spring Bootの@ModelAttributeとは?Formに値が入る仕組みと使い方 @ModelAttribute は、リクエストパラメータをFormクラスへ詰めたり、画面へ渡すモデル属性として扱ったりするために使われます。Thymeleafのフォームでは、Form名、フィールド名、Controllerの引数名がずれると値が入らない原因になります。 記事を読む

まとめ

Spring Bootの入力値の受け取り方は、アノテーション名から覚えるより、リクエストのどこに値があるかで整理すると迷いにくいです。

URLの一部なら@PathVariable、クエリなら@RequestParam、JSONなら@RequestBody、画面フォームならFormクラス。この基本を押さえるだけで、現場コードはかなり読みやすくなります。

-実装・エラー解決
-,