Spring MVCは、Spring Frameworkに含まれるServlet API上のWebフレームワークです。正式名称は「Spring Web MVC」で、実体はMavenの座標 org.springframework:spring-webmvc のモジュール1つです。2026年10月20日時点のGA最新はSpring Framework 7.0.9、これを取り込むSpring Bootの最新は4.1.1です。
この記事では定義と処理の流れに加えて、Spring Boot 4.1.1とJava 25.0.4.1で実際にアプリを作り、画面とJSONを返すまでの手順、Callable の同時処理数、Spring Framework 7.0で入ったAPIバージョニングの挙動を測った結果を載せています。
まとめ:Spring MVCの正体とSpring Boot 4.1で使うときの要点
- Spring MVC=Spring Web MVC=
spring-webmvcモジュールです。呼び方が違うだけで同じものを指します。 - すべてのリクエストは
DispatcherServletを通り、HandlerMapping・HandlerAdapter・ViewResolver などの部品へ振り分けられます。Spring Bootでは初回リクエストの時点で初期化されます。 - Spring Boot 4.0以降の依存名は
spring-boot-starter-webmvcです。spring-boot-starter-webは非推奨ですが、取り込まれるspring-webmvcは同じ7.0.9です。 - Spring Bootの既定では、
Callableはコア8スレッドのプールで動きます。1秒かかる処理を24件同時に送ると、同期処理の1.24秒に対して3.54秒かかりました。 - ノンブロッキングが必要なら Spring WebFlux を選びます。ただしJava 21以降なら、先に仮想スレッドを試す価値があります。
Spring MVCの定義:Spring Frameworkのspring-webmvcモジュール
正式名称「Spring Web MVC」と4通りの表記
Spring Frameworkの公式リファレンスは、Spring Web MVCを「Servlet APIの上に構築された最初のWebフレームワークで、Spring Frameworkの当初から含まれている」と説明しています。続けて、正式名称はソースモジュール spring-webmvc の名前に由来し、一般には「Spring MVC」と呼ばれる、と書いています。
検索結果に並ぶSpring MVC、Spring Web MVC、spring-webmvc、org.springframework:spring-webmvc は、すべて同じモジュールを指します。Spring Frameworkの中での位置づけ(DIコンテナやAOPとの関係)は Spring Frameworkとは?特徴・DI/AOPとSpring Bootとの違い・最新版7を解説 にまとめています。
Spring Bootを使わずにspring-webmvcを入れる依存
Spring Bootを使わず、外部のサーブレットコンテナへWARを配備する構成では、次の依存を直接書きます。Maven Centralで確認した7.0系の最新は7.0.9で、7.1.0-M2はマイルストーン版です。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>7.0.9</version>
</dependency>
DIコンテナの設定や DispatcherServlet の登録をすべて自分で組むことになるため、既存のWAR配備案件など、自動構成を使わず設定を管理したい場合の選択肢になります。
DispatcherServletを起点としたリクエスト処理の流れ
公式ドキュメントの DispatcherServlet の章によれば、Spring MVCはフロントコントローラパターンで設計されています。中央の DispatcherServlet がリクエスト処理の共通の手順を持ち、実際の作業は差し替え可能な部品が担います。部品はSpringの設定(DIコンテナ)から探し出されます。
1件のリクエストは次の順に流れます。
- Servletコンテナ(Tomcatなど)が受け取ったリクエストを
DispatcherServletが引き取る HandlerMappingがURLとHTTPメソッドから、呼び出すコントローラのメソッドを決めるHandlerAdapterが引数を組み立ててメソッドを呼び、戻り値を受け取る- 戻り値がビュー名なら
ViewResolverがテンプレートを特定し、@ResponseBody/@RestControllerの戻り値やResponseEntityの本文なら、メッセージコンバータがJSONなどの応答形式に変換する - 途中で例外が出たら
HandlerExceptionResolverがエラー応答に変換する
この5段は自分で書かず、変えたい部分だけBeanを差し替えます。URLの解決規則なら HandlerMapping、エラー応答の形式をそろえたいなら @ControllerAdvice です。エラー応答の標準形式は RFC 9457(Problem Details)とは? で扱っています。
Spring Bootで起動すると、DispatcherServlet は起動時ではなく最初のリクエストで初期化されます。Spring Boot 4.1.1で動かしたときのログは次のとおりで、起動が完了した後、最初のリクエストを受けたTomcatのスレッド(exec-1)が初期化しています。
INFO --- [main] o.s.boot.tomcat.TomcatWebServer : Tomcat started on port 18533 (http) with context path '/'
INFO --- [main] com.example.demo.DemoApplication : Started DemoApplication in 13.5 seconds
INFO --- [io-18533-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
INFO --- [io-18533-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 2 ms
これは spring.mvc.servlet.load-on-startup の既定値が -1 のためです。初回リクエストの数ミリ秒の遅れも避けたい場合(ロードバランサのヘルスチェック直後に本番トラフィックが来る構成など)は、この値を1にすると起動時に初期化されます。
Spring Boot 4.1でSpring MVCアプリを動かす手順
Initializrで生成されるpom.xmlの依存
start.spring.ioでSpring Boot 4.1.1、依存に「Spring Web」と「Thymeleaf」を選んで生成すると、pom.xmlには次の4つが書き出されます。spring-boot-starter-web は出てきません。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc-test</artifactId>
<scope>test</scope>
</dependency>
Spring Boot 4.1.1の動作要件はJava 17以上(26まで)、組み込みサーバーはTomcat 11.0系(今回の実行では11.0.24)です。Initializrでの生成手順そのものは Spring Bootとは|4.1系の依存名の変更と最小Web APIの作り方 に載せています。
画面を返す@ControllerとThymeleafテンプレート
コントローラのクラスは、生成された DemoApplication と同じパッケージ(ここでは com.example.demo)かその配下に置きます。外に置くとコンポーネントスキャンの対象にならず、URLが404になります。画面を返すコントローラは @Controller を付け、ビュー名を文字列で返します。Model に入れた値がテンプレートへ渡ります。
package com.example.demo;
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 PageController {
@GetMapping("/hello")
public String hello(@RequestParam(defaultValue = "Spring") String name, Model model) {
model.addAttribute("name", name);
return "hello";
}
}
戻り値の "hello" は、ThymeleafのViewResolverによって src/main/resources/templates/hello.html に解決されます。テンプレートは次のとおりです。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body><p th:text="'Hello, ' + ${name}">placeholder</p></body>
</html>
プロジェクト直下で ./mvnw spring-boot:run を実行し、http://localhost:8080/hello?name=issoh を開くと <p>Hello, issoh</p> が返りました。ViewResolverは自分で登録していません。Thymeleafの依存を入れた時点で自動構成が登録します。th: 属性の書き方は Thymeleaf(タイムリーフ)とは?読み方とSpring Bootでの使い方 で詳しく扱っています。
JSONを返す@RestController
APIは @RestController を使います。この例のMap型の戻り値は、テンプレートを経由せず、JacksonのメッセージコンバータでJSONになります。
package com.example.demo;
import java.util.Map;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class GreetingController {
@GetMapping("/api/greeting")
public Map<String, String> greeting(@RequestParam String name) {
return Map.of("message", "Hello, " + name);
}
}
/api/greeting?name=a の応答は {"message":"Hello, a"} でした。画面なら @Controller、APIなら @RestController。Spring MVCで最初に覚える分岐はこれです。ほかのアノテーションは Spring Bootのアノテーション一覧 にまとめています。
Spring BootのMVCパターン:Controller・Service・Repositoryの分担
MVCは、アプリをModel(データと業務ロジック)、View(表示)、Controller(入力の受け付け)に分ける設計パターンです。Spring MVCでは、Controllerが @Controller/@RestController のクラス、ViewがThymeleafなどのビュー(通常のJSON応答はViewを経由せずメッセージコンバータで生成)、Modelがコントローラからビューへ渡す Model とその中身のドメインオブジェクトに当たります。
実務のSpring Bootアプリでは、Modelの部分をさらに層に割ります。
| 層 | 付けるアノテーション | 担当 |
|---|---|---|
| Controller | @Controller/@RestController | 入力の検証と応答の形 |
| Service | @Service | 業務ルールとトランザクション |
| Repository | @Repository/Spring Data | DBの読み書き |
Spring MVCはコントローラの呼び出しに加え、モデルの受け渡しやビューの解決も担います。ServiceとRepositoryはアプリケーションの責務を分ける層であり、それぞれがSpring Frameworkの独立したモジュールを指すわけではありません。よくある失敗は、コントローラのメソッドに業務ロジックとSQLを直接書くことです。テストでHTTPを通さないと業務ロジックを確かめられなくなるうえ、同じ処理を別のAPIから呼べなくなります。コントローラは入力の受け取り・検証、Serviceの呼び出し、HTTP応答の組み立てを担当し、業務ルールやSQLは持たせないようにしてください。Service層の書き方は サービスクラスとは?Java・Springでの役割と書き方 を参照してください。パターンとしてのMVCとMVVM・MVPの違いは MVCとは?MVVM・MVPとの違いを同じ機能のコードで比較 にあります。
Spring Boot 4の依存名:starter-webからstarter-webmvcへ
Spring Boot 4.0の移行ガイドは、モジュールの再編でいくつかのstarterが改称され、旧starterは残るものの非推奨で、将来のリリースで削除されると書いています。Web関連は次の2つです。
| 非推奨のstarter | 置き換え先 | 境界の版 |
|---|---|---|
| spring-boot-starter-web | spring-boot-starter-webmvc | 4.0.0 |
| spring-boot-starter-web-services | spring-boot-starter-webservices | 4.0.0 |
境界はMaven Centralのpomで確かめられます。spring-boot-starter-web のdescriptionは、3.5.16では従来の説明文だけですが、4.1.1では末尾に「deprecated in favor of spring-boot-starter-webmvc」が付きます。
名前が変わっても中身は同じです。4.1.1の spring-boot-starter-webmvc は spring-boot-webmvc:4.1.1 を経由して spring-webmvc:7.0.9 を取り込み、旧 spring-boot-starter-web:4.1.1 も同じ経路をたどります。Spring Bootの各版が管理する依存の版は、BOM(spring-boot-dependencies)の spring-framework.version で確認できます。
移行の順序だけは間違えないでください。spring-boot-starter-webmvc は4.0.0-M1以降にしか公開されていません。3.x系のまま依存名だけを書き換えると、依存の解決に失敗します。置き換えは親POM(またはBOM)を4系へ上げる変更と同時か、その後に行います。3系のサポート期限は Spring Bootバージョン一覧とサポート期限(EOL) で確認できます。
Spring MVCとSpring Bootの違い
この2つは競合しません。Spring MVCはリクエストを処理するWebフレームワーク、Spring BootはSpring MVCを含むモジュール群を自動構成して起動できる状態にする仕組みです。
| 観点 | Spring MVC | Spring Boot |
|---|---|---|
| 正体 | spring-webmvcモジュール | 自動構成と起動の仕組み |
| 最新版(2026年10月) | Spring Framework 7.0.9 | Spring Boot 4.1.1 |
| 設定 | Java ConfigかXMLを手で書く | 自動構成+application.properties |
| 実行形態 | Servletコンテナ上(外部・組み込み) | 実行可能JAR(WARも可) |
| Web以外の用途 | なし | バッチ・CLIにも使う |
学ぶ順番で迷ったら、Spring Bootから入って構いません。前章の手順で書いたコントローラがそのままSpring MVCのコードです。XML設定から組み立てる学習は、レガシー案件を保守する予定がなければ遠回りです。
Callableの既定8スレッドとDeferredResultの実行元の違い
Callableが動くスレッドとServletスレッドの解放
Spring MVCは1リクエストを1スレッドで処理します。コントローラの戻り値を Callable にすると、公式ドキュメントの非同期リクエストの章にあるとおり、Spring MVCが request.startAsync() を呼び、Callable を AsyncTaskExecutor に渡して別スレッドで実行します。その間、Servletコンテナのスレッドは解放され、レスポンスは開いたままになります。
@GetMapping("/slow")
@ResponseBody
public Callable<String> slow() {
return () -> {
Thread.sleep(1000);
return Thread.currentThread().getName();
};
}
Spring Boot 4.1.1でこのメソッドを呼ぶと、戻ってきたスレッド名は task-1 でした。Tomcatのスレッド(http-nio-…-exec-…)ではありません。Spring Bootのタスク実行の章によれば、自動構成された applicationTaskExecutor がSpring MVCの非同期処理に使われ、このスレッドプールは8本のコアスレッドで動きます。
24件同時の実測:同期1.24秒、Callable 3.54秒
1秒かかる処理を、同期で返すエンドポイントと Callable で返すエンドポイントに用意し、24件を同時に送って全件が返るまでの時間を測りました。環境はSpring Boot 4.1.1、Java 25.0.4.1、macOS上のTomcat 11.0.24です。起動後に1件だけ送って温めてから、次のPythonスクリプトで24件を同時に送り、最初の送信から最後の応答までを測りました(同期の計測ではURLの /slow を /sync に変えています)。同期とCallable既定は2回とも同じ値(1.24秒・3.54秒)、仮想スレッドは1.26秒と1.25秒、core-size=24は2回とも1.25秒で、表には2回目の値を載せています。
import concurrent.futures as cf, time, urllib.request
def call(_):
return urllib.request.urlopen("http://localhost:8080/slow").read().decode()
start = time.time()
with cf.ThreadPoolExecutor(24) as ex:
names = list(ex.map(call, range(24)))
print(round(time.time() - start, 2), sorted(set(names)))
| 条件 | 24件の所要時間 | 処理したスレッド |
|---|---|---|
| 同期(Callableなし) | 1.24秒 | Tomcat 24本 |
| Callable・Bootの既定 | 3.54秒 | task-1〜task-8の8本 |
| Callable・仮想スレッド有効 | 1.25秒 | 仮想スレッド |
| Callable・core-size=24 | 1.25秒 | task-1〜task-24 |
既定のままだと、8件ずつ3回に分かれて処理されたため3秒以上かかりました。Tomcatのワーカースレッドの上限は既定で200本(server.tomcat.threads.max)なので、この条件では Callable にしたほうが3倍遅くなります。Bootのスレッドプールはキューが無制限のため、キューがあふれたときに限ってスレッドを増やす仕組みが働かず、8本のまま待たせます。この applicationTaskExecutor は @Async のメソッドとも共有されます。
Callableを使うときの設定とDeferredResultの使いどころ
Java 21以降なら、spring.threads.virtual.enabled=true を設定するのが最も手軽です。Tomcatと applicationTaskExecutor の両方が仮想スレッドで動き、上の実測では1.25秒になりました。仮想スレッドの仕組みと注意点は Java Virtual Thread(仮想スレッド)とProject Loomとは? にあります。Java 17に留まる場合は、spring.task.execution.pool.core-size を見込みの同時処理数に合わせるか、queue-capacity と max-size を併せて設定します。
次の場合は Callable ではなく DeferredResult を使います。JMSメッセージの受信処理やスケジュールされたタスクなど、アプリ側で管理する別のスレッドから結果を渡す場合です。コントローラは DeferredResult を返して保存しておき、結果が来た側のスレッドで setResult() を呼びます。
「処理が遅いから」という理由で全エンドポイントに付けるのはやめてください。Callable は別のプールのスレッドを使うだけで、ブロッキングの待ち時間は消えません。主な効果は、長時間処理の間にServletコンテナのスレッドを解放できることです。応答が速くなるかは、Tomcatと非同期用のプールのどちらが先に詰まるかで決まり、上の実測のように既定のままでは遅くなることもあります。プールの大きさとタイムアウト(spring.mvc.async.request-timeout)は負荷試験の結果で決めます。
Spring Framework 7.0のAPIバージョニングを入れるときの落とし穴
Spring Framework 7.0で、@GetMapping などに version 属性が追加されました(RequestMapping#version は @since 7.0)。Spring Bootでは spring.mvc.apiversion.use.header=API-Version のように、版をどこから読むかを設定するだけで使えます。基本の書き方は Spring Boot 4とは?最新バージョン4.1の変更点・新機能 で紹介しています。
既存のSpring MVCアプリに後から入れると、3つの挙動でつまずきます。Spring Boot 4.1.1で確かめた結果です。
- 版の指定が無いリクエストは、version属性を付けていない画面まで400になる:
use.headerだけを設定すると、/helloのような普通の@Controllerも400 Bad Requestを返しました。公式ドキュメントどおり、バージョニングを有効にすると版の指定は既定で必須になります。 spring.mvc.apiversion.required=falseにすると、ヘッダーなしのリクエストは最高版へ行く:400は消えますが、ヘッダーを送らない古いクライアントの応答が2.0版に変わりました。ソースの説明では、版の無いリクエストは「どの版でも受け入れる」扱いになります。- 既存のクライアントを守るなら
spring.mvc.apiversion.default=1.0:既定の版を設定すると必須ではなくなり(ApiVersionConfigurerの説明どおり)、ヘッダーなしは1.0へ、画面も200に戻りました。
逆に、版の取得元を設定せずに version 属性だけを書くと、起動時に IllegalStateException: API version specified, but no ApiVersionStrategy configured で止まります。
Spring MVCを選ばない場面:WebFluxへ切り替える条件
Spring MVCはServlet APIの上で動き、Spring Boot 4.1.1はServlet 6.1以上のコンテナを前提にしています。Spring Framework 5.0で追加されたSpring WebFluxは別の spring-webflux モジュールで、ノンブロッキングのリアクティブスタックです。
Spring MVCでも SseEmitter や StreamingResponseBody でServer-Sent Eventsやストリーミングを配信できます。WebFluxへの切り替えを考えるのは、ストリーミングの書き込みもノンブロッキングにする必要がある場合と、外部への通信待ちが大半でアプリ全体をリアクティブに書けるチームがいる場合です。
「同時接続が多くてスレッドが足りない」だけが理由なら、Java 21以降では先に仮想スレッドを試してください。前章の実測のとおり、設定1行で待ち時間の長いリクエストを並べて処理でき、コントローラのコードもJDBCもそのまま使えます。JDBCで同期的にDBを叩く処理が中心のアプリや、チームにリアクティブの経験がない場合にWebFluxへ全面移行すると、学習と書き直しのコストに見合いません。判断材料は Spring WebFluxとは?MVCとの違い・Mono/Fluxの基本 にあります。
Spring MVCでの新規開発や、古いJava Webアプリの作り直しを外部に任せたい場合は、一創のフルスクラッチ開発でご相談を受け付けています。
Spring MVCの依存・公式資料・移行に関するよくある質問
「Spring Boot MVC」という名前のフレームワークはありますか?
ありません。Spring BootでSpring MVCを使う構成を、慣用的にSpring Boot MVCと呼んでいるだけです。Maven Centralにあるのは org.springframework:spring-webmvc(本体)と org.springframework.boot:spring-boot-starter-webmvc(Spring Bootのstarter)で、後者が前者を取り込みます。
spring-webmvcの最新バージョンはいくつですか?
2026年10月20日時点のGA最新は7.0.9です。7.1.0-M2はマイルストーン版なので本番には使いません。Spring Bootを使う場合は自分で版を書かず、Spring BootのBOMに任せます(4.1.1なら7.0.9)。最新の版はMaven Centralの org/springframework/spring-webmvc で確認できます。
Spring MVCの公式ドキュメントはどこにありますか?
docs.spring.ioのSpring Frameworkリファレンス「Web on Servlet Stack」の章が一次情報です。日本語訳はPleiades(spring.pleiades.io)が同じ構成で公開していますが、本家への追随に時差があるため、版に依存する記述は英語の本家で確かめてください。
Spring MVCとMVVM・MVPの違いは何ですか?
Spring MVCはサーバー側でHTTPリクエストを処理するフレームワーク、MVVMやMVPはUI層の責務の分け方を指す設計パターンで、層が違います。サーバー側をSpring MVC、ブラウザ側をMVVM系のフレームワークで組む構成も普通にあります。
Seasar2やStrutsからの移行先としてSpring MVCは選べますか?
選べます。SeasarプロジェクトはSeasar2を含む多くのプロダクトを2016年9月26日にEOLとし、Apache Strutsも1.x系を2013年4月5日にEOLとしています。移行では、アクションクラスをコントローラへ、設定ファイルの画面遷移の定義をアノテーションへ置き換える作業が中心です。具体的な対応表は StrutsとSpringの違いと移行手順 にあります。