24 人が閲覧(直近 30 日) Spring

Spring WebFluxとは?MVCとの違い・Mono/Fluxの基本とSpring Boot 4.1での使い方

Spring WebFluxとは?MVCとの違い・Mono/Fluxの基本とSpring Boot 4.1での使い方

Spring WebFluxは、Spring Frameworkに含まれるノンブロッキングなWebフレームワークです。2017年9月のSpring Framework 5.0で加わり、2026年9月時点の最新はSpring Framework 7.0.9(Spring Boot 4.1.1が採用する版)です。少数のイベントループスレッドで多数のリクエストをさばくため、スレッドを止めない前提で書く必要があります。

この記事では、Spring MVCとの違い、ReactorのMonoとFlux、Spring Boot 4.1.1での最小構成、ブロッキング処理を混ぜたときの挙動を実測値つきで解説します。WebFluxを選ぶべきか迷っている方は、先にまとめと「採用すべき場面と避けるべき場面」の章を読んでください。

まとめ:Spring WebFluxの要点とMVCとの選び分け

  • Spring WebFluxはReactive Streamsに基づくノンブロッキングなWebフレームワークで、既定のサーバーはNetty(Reactor Netty)です。
  • Spring MVCは「リクエストごとにスレッドを占有してよい」前提、WebFluxは「スレッドを止めない」前提です。違いはこの前提と、それを受けたスレッドモデルにあります。
  • 戻り値は1件ならMono(0〜1件)、複数件やストリームならFlux(0〜N件)を使います。
  • JPAやJDBCのようなブロッキングAPIが中心なら、公式リファレンスもSpring MVCを推奨しています。WebFluxからリレーショナルDBへノンブロッキングでアクセスするなら、対応ドライバーのあるR2DBCが候補です。MongoDBなどには別のリアクティブ対応があります。
  • Spring Boot 4ではWebClientの自動構成が別スターター(spring-boot-starter-webclient)に分かれました。spring-boot-starter-webfluxだけではWebClient.BuilderのBeanが作られません。
  • 動いているMVCアプリを移行する必要はありません。まず外部API呼び出しだけWebClientに置き換え、効果を測ってから広げます。Java 21以降では仮想スレッドという選択肢もあります。

Spring WebFluxの定義と2026年9月時点の構成バージョン

WebFluxは、Spring Frameworkのspring-webfluxモジュールです。Servlet APIに依存するSpring MVC(spring-webmvc)とは別系統の「リアクティブスタック」として、Spring Framework 5.0(GitHubのタグv5.0.0.RELEASEは2017年9月28日)で追加されました。リアクティブライブラリにはProject Reactorを使い、Reactive Streams仕様のバックプレッシャー(受け手が処理できる量だけ要求する仕組み)に対応します。

Spring Boot 4.1.1(2026年8月20日公開)でspring-boot-starter-webfluxを使った場合、BOM(依存バージョンをまとめて管理する定義)が引き込む主要ライブラリの版は次のとおりです。最新の対応表はSpring BootリファレンスのManaged Dependency Coordinatesで確認できます。

ライブラリ バージョン 役割
Spring Framework(spring-webflux) 7.0.9 WebFlux本体
Reactor Core 3.8.7 Mono・Flux
Reactor Netty 1.3.7 HTTPサーバー/クライアント
Netty 4.2.17.Final 非同期I/O基盤
Jackson 3.1.5 JSON変換

Spring Boot 4.1系のOSSサポートは2027年7月31日までです。4.0系は2026年12月31日で終わり、3.5系は2026年6月30日に終了しました。版ごとのサポート期限はSpring Bootのバージョン一覧とサポート期限で、4系の変更点全体はSpring Boot 4の変更点と3との違いで整理しています。

Spring MVCとの違い:スレッドモデルとブロッキングの前提

公式リファレンスの「Concurrency Model」は、両者の違いを「ブロッキングとスレッドに関する既定の前提」だと説明しています。Spring MVCはアプリケーションが現在のスレッドをブロックしうると考え、Servletコンテナは大きなスレッドプールでそのブロックを吸収します。WebFluxはアプリケーションがブロックしないと考え、少数の固定スレッド(イベントループ)でリクエストを処理します。

Reactor Nettyのイベントループ数は、既定で「JVMが使えるプロセッサ数(最小4)」です。16論理CPUのマシンでSpring Boot 4.1.1を起動すると、リクエストを処理するスレッドはreactor-http-nio-1からreactor-http-nio-16の16本でした。Tomcatの既定最大200スレッドとは桁が違います。

比較項目 Spring MVC Spring WebFlux
基盤API Servlet API Reactive Streams
既定サーバー Tomcat Netty(Reactor Netty)
他の対応サーバー Jetty Tomcat・Jetty
スレッドの持ち方 1リクエスト1スレッド 少数のイベントループ
ブロッキング呼び出し 許容 イベントループ上では不可
コントローラーの戻り値 通常の型・Mono・Fluxなど Mono・Flux・通常の型など
書き方 アノテーション/関数型 アノテーション/関数型
DBアクセス(RDB) JDBC・JPA R2DBC
Boot 4のスターター spring-boot-starter-webmvc spring-boot-starter-webflux

@RestControllerや@GetMappingといったアノテーションは両方で使えるので、コードの見た目は似ています。一方、同じ書き方で中にブロッキング処理を入れると、WebFluxでは次の章で示すとおり性能が大きく落ちます。Spring MVC側の構成とSpring Boot 4での依存名の変更はSpring MVCの解説記事にまとめています。

両方のスターターを依存に入れた場合、Spring BootはSpring MVCを自動構成します。MVCアプリにWebClientだけを使う目的でWebFluxを追加する開発者が多いため、とSpring Bootのリファレンスは理由を説明しています。WebFluxで起動させたい場合はSpringApplication.setWebApplicationType(WebApplicationType.REACTIVE)で明示します。

MonoとFluxの基本とReactive Streams

Mono(0〜1件)とFlux(0〜N件)の使い分け

Reactorのリファレンスは、Flux<T>を「0〜N個の要素を流す非同期シーケンス」、Mono<T>を「0〜1個の結果」と定義しています。どちらもReactive StreamsのPublisherで、正常終了時はonComplete、異常終了時はonErrorを通知します。ただし、終了しないストリームや、終了シグナルを通知せず購読がキャンセルされる場合もあります。

  • IDで1件取得する、登録結果を返す:Mono<User>
  • 一覧を返す、Server-Sent Eventsで流し続ける:Flux<User>
  • 本文を返さない(削除など):Mono<Void>

MonoやFluxを返すだけでは、処理が購読時まで遅延されるとは限りません。cold publisherは購読(subscribe)を契機に処理を始めますが、hot publisherは購読前から動く場合があります。Mono.just(load())ではload()がその場で実行され、Mono.fromCallable(() -> load())なら購読時まで遅延されます。WebFluxでは戻り値のPublisherをフレームワークが購読します。コントローラー内でblock()を呼んで値を取り出すと、イベントループを止める原因になります。

Reactorの主要オペレーターと処理の使い分け

MonoやFluxは、オペレーターをつないで処理を宣言します。最初に覚えるのは次の6つで足ります。

オペレーター 用途
map 値を同期的に変換
flatMap 値から別のMono/Fluxを呼び出して平坦化
filter 条件に合う要素だけ残す
zip 複数の結果を待ち合わせて結合
onErrorResume エラー時に代替値へ切り替え
timeout 応答の上限時間を設定

mapとflatMapの取り違えが最初のつまずきです。変換先が普通の値ならmap、WebClientやR2DBCの呼び出しのようにMonoを返す処理ならflatMapを使います。mapでMonoを返すと、型がMono<Mono<T>>になり、内側は誰にも購読されません。

Reactive Streamsとバックプレッシャーの位置づけ

Reactive Streamsは、Publisher・Subscriber・Subscription・Processorの4インターフェースで非同期ストリームの約束事を定めた仕様です。受け手がrequest(n)で処理できる件数を伝え、送り手はそれを超えて送りません。WebFluxはこの仕組みをHTTPの読み書きまで通しているので、遅いクライアントに大量データを流してもメモリにため込みにくくなります。流量制御の方式の比較はバックプレッシャーの仕組みと実装方式の選び分けで扱っています。

Spring Boot 4.1で最小のWebFluxアプリを作る手順

依存関係とspring-boot-starter-webfluxの中身

Spring Initializrで「Spring Reactive Web」を選び、Mavenプロジェクトを生成します。以下は依存関係部分の抜粋です。生成された@SpringBootApplication付きの起動クラスは残し、各サンプルを同じパッケージに配置してください。Spring Boot 4.1.1のspring-boot-starter-webfluxは、Jackson、Reactor Netty、spring-webfluxをまとめて引き込みます。

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>4.1.1</version>
</parent>

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux-test</artifactId>
    <scope>test</scope>
  </dependency>
</dependencies>

テスト用のspring-boot-starter-webflux-testはSpring Boot 4で追加されたスターターで、WebTestClientとreactor-test(StepVerifier)を含みます。Spring Boot 3までのspring-boot-starter-testだけの構成から移行する場合は、これを足します。

アノテーション方式のコントローラー

Spring MVCと同じ@RestControllerで書けます。この例では戻り値にMono・Fluxを使います。Spring MVCもこれらの型を扱えるため、戻り値だけでは両者を区別できません。

package com.example.demo;

import java.time.Duration;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@RestController
public class GreetingController {

    @GetMapping("/hello/{name}")
    public Mono<String> hello(@PathVariable String name) {
        return Mono.just("Hello, " + name + " (" + Thread.currentThread().getName() + ")");
    }

    @GetMapping(value = "/ticks", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<Long> ticks() {
        return Flux.interval(Duration.ofSeconds(1)).take(3);
    }
}

関数型エンドポイント(RouterFunction)の書き方

WebFluxには、ルーティングとハンドラーをラムダで書く関数型エンドポイントもあります。公式リファレンスは、小規模なアプリケーションや要件の単純なマイクロサービスで透明性と制御を重視する場合の選択肢に挙げています。アノテーション方式と同じアプリ内に共存できます。

package com.example.demo;

import static org.springframework.web.reactive.function.server.RouterFunctions.route;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;

@Configuration
public class GreetingRouter {

    @Bean
    RouterFunction<ServerResponse> greetingRoutes() {
        return route()
                .GET("/fn/hello", request -> ServerResponse.ok()
                        .bodyValue("Hello from functional endpoint"))
                .build();
    }
}

起動ログとcurlでの動作確認

プロジェクト直下で./mvnw spring-boot:runを実行します。Java 25.0.4.1で起動すると、ログにNettyWebServer : Netty started on port 8080 (http)と出ます。Tomcatではなく、Nettyが立ち上がったことをここで確認できます。

$ curl localhost:8080/hello/issoh
Hello, issoh (reactor-http-nio-2)

$ curl localhost:8080/fn/hello
Hello from functional endpoint

$ curl -N localhost:8080/ticks
data:0

data:1

data:2

1つ目の応答に出ているreactor-http-nio-2がイベントループのスレッド名です。/ticksはServer-Sent Eventsとして1秒ごとに1件ずつ届きます。

ブロッキング処理の扱い:イベントループを止めない実装と実測

WebFluxで性能問題の原因になりやすいのは、イベントループ上でのブロッキング呼び出しです。JDBC、JPA、Thread.sleep、同期HTTPクライアント、ファイルI/Oなどが該当します。16本しかないスレッドのうち1本が止まると、そのスレッドに割り当てられた他のリクエストも待たされます。

1秒かかるブロッキング処理を、イベントループ上で直接実行するエンドポイントと、ReactorのboundedElasticスケジューラーへ逃がすエンドポイントを用意し、64リクエストを同時に送って比較しました。

@GetMapping("/bad")
public Mono<String> bad() throws InterruptedException {
    Thread.sleep(1000);
    return Mono.just(Thread.currentThread().getName());
}

@GetMapping("/good")
public Mono<String> good() {
    return Mono.fromCallable(() -> {
                Thread.sleep(1000);
                return Thread.currentThread().getName();
            })
            .subscribeOn(Schedulers.boundedElastic());
}
実装 64件の所要時間 処理したスレッド
イベントループ上で直接ブロック 4.60〜5.30秒 reactor-http-nio 16本
boundedElasticへ逃がす 1.30〜1.31秒 boundedElastic 64本
Stringを返す+仮想スレッド有効 1.34秒 仮想スレッド 64本

計測環境は16論理CPUのMac(x86_64)、Spring Boot 4.1.1、Java 25.0.4.1です。負荷はPythonのurllibと64スレッドのThreadPoolExecutorで64件を同時に送り、全件の応答までの時間を測りました。1行目と2行目は2回ずつ計測した範囲、3行目は1回の値です。直接ブロックした場合、16本のイベントループが1秒ずつ占有されるので、64件は最低でも4巡(約4秒)かかります。boundedElasticへ逃がすと、64件がほぼ同時に1秒で終わりました。

3行目は、戻り値をMonoにせずStringを返すコントローラーで、Thread.sleep(1000)を呼んだ場合です。Spring Framework 6.1以降のWebFluxは、リアクティブ型以外を返すコントローラーメソッドを「ブロッキング」とみなし、AsyncTaskExecutorが設定されていればそちらで実行します。Spring Bootでspring.threads.virtual.enabled=trueにすると、このメソッドは仮想スレッドで動きました。設定しない場合は同じメソッドがreactor-http-nio上で動き、64件に4.75秒かかりました。

ただし、boundedElasticは既定でCPUコア数の10倍までしかスレッドを作らない有限のプールです。ブロッキング処理が大半を占めるアプリなら、それはSpring MVCのスレッドプールを手作りしているのと変わりません。逃がすのは一部の遅い呼び出しに限り、DBアクセスそのものをノンブロッキングにしたい場合はR2DBC(spring-boot-starter-data-r2dbc)を使います。JPAにはリアクティブ版がないため、「spring boot reactive jpa」という組み合わせはそのままでは成立しません。

WebClientによる外部API呼び出しとSpring Boot 4の依存変更

WebClientは、WebFluxと同じspring-webfluxモジュールに入っているノンブロッキングなHTTPクライアントです。公式リファレンスは、Spring MVCアプリケーションからでもWebClientを使えるとし、リアクティブ化の最初の一歩として勧めています。

Spring Boot 4で注意が必要なのが依存関係です。Spring Boot 4.1.1でspring-boot-starter-webfluxだけを入れた状態では、WebClient.BuilderのBeanが0件で、注入するとアプリが起動しません。WebClientの自動構成はspring-boot-starter-webclient(中身はspring-boot-webclientとreactor-netty-http)に分かれており、これを追加するとBeanが1件作られます。Spring Boot 3のサンプルをそのまま写すと、ここでつまずきます。

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-webclient</artifactId>
</dependency>
package com.example.demo;

import java.time.Duration;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

@Component
public class GreetingClient {

    private final WebClient webClient;

    public GreetingClient(WebClient.Builder builder) {
        this.webClient = builder.baseUrl("http://localhost:8080").build();
    }

    public Mono<String> fetch(String name) {
        return webClient.get()
                .uri("/hello/{name}", name)
                .retrieve()
                .bodyToMono(String.class)
                .timeout(Duration.ofSeconds(2))
                .onErrorResume(java.util.concurrent.TimeoutException.class,
                        e -> Mono.just("fallback"));
    }
}

ブロッキングなSpring MVCアプリで同期的にHTTPを呼ぶだけなら、WebClientよりRestClientが素直です。使い分けとRestTemplateからの移行はSpringのRestClientの使い方で解説しています。

WebFluxを採用すべき場面と避けるべき場面

公式リファレンスの「Applicability」は、動作しているSpring MVCアプリケーションを変える必要はないと明言しています。さらに、幅広いアプリケーションで移行は不要だろうと述べています。WebFluxの採用は「新しいから」ではなく、次の条件で判断します。

WebFluxが向いている条件

  • 外部APIやマイクロサービスへの呼び出しが多く、1回の待ち時間が長い、または呼び出し同士の依存が深い(公式は、この2つが大きいほど効果も大きいとしています)。
  • Server-Sent EventsやWebSocketで、多数のクライアントへ長時間ストリームを流す。
  • Spring Cloud Gatewayのように、I/O待ちが処理の大半を占めるゲートウェイやプロキシを作る。
  • DBがR2DBC対応(PostgreSQL、MySQL、SQL Serverなど)で、データアクセスまでノンブロッキングで通せる。

WebFluxを避けるべき条件

  • JPA・JDBC・同期SDKなどブロッキングなライブラリが中心。公式も、この場合は少なくとも一般的な構成ではSpring MVCが最適としています。
  • チームにリアクティブの経験者がいない大規模開発。公式は、ノンブロッキング・関数型・宣言型への移行は学習曲線が急だと注意しています。スタックトレースが追いにくく、障害調査の難度も上がります。
  • CPU負荷の高い計算が中心で、I/O待ちが少ない。WebFluxに変えても計算量は減らず、重い計算をイベントループ上で実行すると他のリクエスト処理を遅らせます。

Spring MVCの仮想スレッド利用とWebFluxとの選択基準

Java 21以降なら、Spring MVCのままspring.threads.virtual.enabled=trueを設定して仮想スレッドを使う方法もあります(Spring Bootリファレンスの「Virtual threads」)。ブロッキングな書き方のまま、待ち時間中にOSスレッドを手放せるので、「I/O待ちでスレッドが足りない」という課題の多くはこれで解決します。Spring Bootのリファレンスは、Java 24以降を強く推奨しています。また、仮想スレッドがキャリアスレッドに固定される「ピン留め」でスループットが落ちる場合があると注意しています。

前章の実測のとおり、WebFluxのアプリでも仮想スレッドを有効にすれば、Stringなどを返すブロッキングなコントローラーだけを仮想スレッドへ回せます。既存のMVCアプリの同時接続数を伸ばしたいなら仮想スレッド、ストリーミングやバックプレッシャー、非同期呼び出しの合成そのものが要件ならWebFlux、という線引きが実用的です。仮想スレッドの仕組みとピン留めの落とし穴はJava Virtual Thread(仮想スレッド)の解説を参照してください。

WebTestClientとStepVerifierによるテスト

WebFluxのコントローラーは@WebFluxTestとWebTestClientで、サーバーを起動せずにテストできます。Spring Boot 4では、@WebFluxTestのパッケージがorg.springframework.boot.webflux.test.autoconfigureに変わりました。

package com.example.demo;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.webflux.test.autoconfigure.WebFluxTest;
import org.springframework.test.web.reactive.server.WebTestClient;

@WebFluxTest(GreetingController.class)
class GreetingControllerTest {

    @Autowired
    WebTestClient client;

    @Test
    void hello() {
        client.get().uri("/hello/issoh").exchange()
                .expectStatus().isOk()
                .expectBody(String.class)
                .value(body -> assertThat(body).startsWith("Hello, issoh"));
    }
}

MonoやFluxそのものを検証するときは、reactor-testのStepVerifierを使います。流れてくる要素と完了シグナルを順番どおりに検証できます。

@Test
void mapAndFilter() {
    Flux<String> flux = Flux.range(1, 5)
            .filter(n -> n % 2 == 1)
            .map(n -> "item-" + n);

    StepVerifier.create(flux)
            .expectNext("item-1", "item-3", "item-5")
            .verifyComplete();
}

この記事のサンプルを含む検証用プロジェクトは、Spring Boot 4.1.1とJava 25.0.4.1の環境でmvn testを実行し、テスト5件がすべて成功することを確認済みです。

よくある質問

Spring WebFluxとは何ですか?

Spring Frameworkに含まれるノンブロッキングなWebフレームワークです。Spring Framework 5.0で追加され、Reactive StreamsとProject Reactorを基盤に、少数のイベントループスレッドで多数のリクエストを処理します。

Spring WebFluxとSpring MVCの違いは何ですか?

前提となるスレッドモデルが違います。Spring MVCはリクエストごとにスレッドを占有しブロッキングを許容し、WebFluxはスレッドを止めない前提でイベントループを使います。アノテーションは共通で、Mono・Fluxも両方で使えます。WebFluxでは通常の値を返すこともできます。

Spring WebFluxの既定のサーバーは何ですか?

Netty(Reactor Netty)です。Spring Boot 4.1.1のspring-boot-starter-webfluxはReactor Netty 1.3.7とNetty 4.2.17.Finalを引き込みます。TomcatやJettyに切り替えることもできます。

Spring WebFluxでJPAは使えますか?

技術的にはboundedElasticなどへ逃がせば呼べますが、ノンブロッキングの利点は失われます。JPAが中心なら公式もSpring MVCを推奨しており、リレーショナルDBへのアクセスまでノンブロッキングにするなら、対応ドライバーのあるR2DBC(Spring Data R2DBC)が候補です。

MonoとFluxはどう使い分けますか?

結果が0〜1件ならMono、0〜N件やストリームならFluxを使います。IDで1件取得する処理はMono、一覧取得やServer-Sent EventsはFluxが典型です。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.09 テックブログ IDCFクラウド(IDCフロンティア)不正アクセス・ランサムウェア:影響先・復旧・データは戻るか
  2. 2026.10.08 テックブログ 大阪公立大学のランサムウェア被害と仮想化基盤の停止|全授業休講に至った経緯とバックアップを守る設定
  3. 2024.11.08 テックブログ OpenAPI GeneratorでJavaコードを自動生成する方法|CLI導入からSpring・ライブラリ選択まで
  4. 2026.10.09 テックブログ ニッスイのサイバー攻撃で日水物流の入出荷停止|委託先クラウド障害に荷主が備える手順
  5. 2026.10.09 テックブログ 京王電鉄のランサムウェア被害とグループ共通基盤:決済・ポイント・予約が止まった範囲と遮断の初動

RELATED POSTS 関連記事

目次