9
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Spring Framework 7 の「APIバージョン管理」機能を理解する

Last updated at Posted at 2025-12-18

この記事は NTTドコモソリューションズ Advent Calendar 2025 19日目の記事です。

はじめに

こんにちは、NTTドコモソリューションズ(旧:コムウェア)の田村です。
普段はMacchinetta Framework、Springプロジェクトに関する社内からの問合せ対応や技術検証を行っています。
昨年はSpring BootにTestcontainersを導入して開発環境のDockerコンテナをテストコードで管理するを投稿していました。

今回はSpring Framework 7のメジャーバージョンアップでリリースされた新機能「APIバージョン管理」について解説をします。
APIバージョン管理は、互換性を保ちながら機能追加や変更を行うための重要な仕組みです。
これまで、Spring開発ではAPIのバージョンを管理するために

  • URI に v1 を入れる
  • ヘッダーでバージョン情報を渡す
  • Accept のメディアタイプで切り替える

などのルールを開発者が独自に実装してきました。
今回リリースされたAPI Versioning(APIバージョン管理)機能を利用するとどのように実現できるのか見ていきたいと思います。
Spring Boot 4 ではプロパティでの設定も提供されています。

Spring 公式のAPIバージョン管理ドキュメントはSpring Web MVC / WebFluxそれぞれに存在しますが、APIバージョン管理の考え方と機能は同等のため本記事のコードはSpring Web MVCベースで解説しています。

本記事で記載している内容は以下バージョンで2025年12月に確認したものです。

  • Spring Framework 7.0.1
  • Spring Boot 4.0.0
  • Java 25

APIバージョン管理の導入

1. 機能の有効化

デフォルトではAPIバージョン管理の機能は有効化されていません。
WebMvcConfigurerBeanを使用し、configureApiVersioning(…​)をオーバーライドするか、プロパティから設定する必要があります。

@Configuration
public class WebConfiguration implements WebMvcConfigurer {

	@Override
	public void configureApiVersioning(ApiVersionConfigurer configurer) {
		configurer.useRequestHeader("API-Version");
	}
}
spring:
  mvc:
    apiversion:
      use:
        header: API-Version

バージョン解決に指定可能なオプションは以下になります。

  • リクエストヘッダー
  • リクエストパラメータ
  • URLパスのセグメント
  • メディアタイプのパラメータ

ApiVersionResolverを登録することで独自定義も可能です。

2. APIのバージョン設定

@RequestMapping / @GetMapping などに version 属性を付与して、メソッド単位で対応バージョンを宣言します。
ベースライン表記(例:"1.2.0+")で「そのバージョン以降」を一括ハンドリングすることもできます。

@RestController
@RequestMapping("/products")
class ProductController {

  // 1.0.0 に対応
  @GetMapping(path = "/{id}", version = "1.0.0")
  public ProductV1 getV1(@PathVariable Long id) { /* ... */ }

  // 1.2.0 以降を「ベースライン」でまとめて対応
  @GetMapping(path = "/{id}", version = "1.2.0+")
  public ProductV2 getBaseline(@PathVariable Long id) { /* ... */ }
}

ベースライン表記はAPIのバージョン管理上のルールであり、実際のリクエストでどのバージョンが許可されているかは別で設定する必要があります。
上記設定で例えばリクエスト時にバージョン"1.3.0"でリクエストが来ることを許容したい場合はApiVersionConfigurerでサポートするバージョンの明記が必要です。

@Configuration
public class WebConfiguration implements WebMvcConfigurer {

	@Override
	public void configureApiVersioning(ApiVersionConfigurer configurer) {
		configurer.useRequestHeader("API-Version");
        configurer.addSupportedVersions("1.3.0"); // 1.3.0 バージョンサポートを追加
	}
}

上記設定をすることで次の3つのバージョンが指定可能になります。

  • 1.0.0
  • 1.2.0
  • 1.3.0

3. セマンティックバージョニング(Semantic Versioning)

APIバージョンの指定はデフォルトで SemanticApiVersionParserによってバージョン文字列をmajor.minor.patchに解析します(未指定の minor/patch は 0)。
バージョンのパース処理をカスタマイズしたい場合はApiVersionParserの実装を独自に定義すれば可能です。

@RestController
@RequestMapping("/products")
class ProductController {

  // 1.0.0 として認識される
  @GetMapping(path = "/{id}", version = "1")
  public ProductV1 getV1(@PathVariable Long id) { /* ... */ }

  // 1.2.0+ として認識される
  @GetMapping(path = "/{id}", version = "1.2+")
  public ProductV2 getBaseline(@PathVariable Long id) { /* ... */ }
}
# 1.3.0として認識される
curl -v -H "API-Version:1.3" localhost:8080/products/1

4. 検証(Validation)とエラーハンドリング

未指定・未サポートのバージョンはMissingApiVersionException / InvalidApiVersionException が発生します。

未指定時はデフォルトでアプリケーションログにWARNログとして出力され、レスポンスのステータスコードは400になります。

WARN 971493 --- [demo] [nio-8080-exec-2] .w.s.m.a.ResponseStatusExceptionResolver : Resolved [org.springframework.web.accept.MissingApiVersionException: 400 BAD_REQUEST "API version is required."]

未サポートバージョン指定時も同様にアプリケーションログにWARNログとして出力され、レスポンスのステータスコードは400になります。

WARN 971493 --- [demo] [nio-8080-exec-3] .w.s.m.a.ResponseStatusExceptionResolver : Resolved [org.springframework.web.accept.InvalidApiVersionException: 400 BAD_REQUEST "Invalid API version: '1.5.0'."]

設定を変更することでバージョン未指定の許容、未指定時に振り分けられるデフォルトバージョンの指定をすることが可能です。

@Configuration
public class WebConfiguration implements WebMvcConfigurer {

	@Override
	public void configureApiVersioning(ApiVersionConfigurer configurer) {
		configurer.useRequestHeader("API-Version");
        configurer.setVersionRequired(false); // 未指定の許容
        configurer.setDefaultVersion("1.0.0"); // デフォルトバージョン
	}
}

未指定は許容するが、デフォルトバージョンは指定しない場合、

  1. バージョン指定がないAPI
  2. 最新のAPI

の優先順位となるようです。

5. 非推奨通知(Deprecation/Sunset ヘッダー)

レスポンスヘッダーを介してクライアントに非推奨バージョン関する情報を送信する設定ができます。
以下のRFC(IETFが公開するインターネット標準仕様の文書)に沿った実装ができるようにStandardApiVersionDeprecationHandlerが用意されています。

  • RFC 8594
    • サービスやリソースの廃止時間を示すヘッダー
  • RFC 9745
    • サービスやリソースの非推奨(廃止予定)を示すヘッダー
@Configuration
public class WebConfiguration implements WebMvcConfigurer {

	@Override
	public void configureApiVersioning(ApiVersionConfigurer configurer) {
		configurer.useRequestHeader("API-Version");
		configurer.setVersionRequired(false);
		configurer.setDefaultVersion("1.2.0");
		configurer.setDeprecationHandler(deprecationHandler()); // ApiVersionDeprecationHandlerの登録
	}

	@Bean
	StandardApiVersionDeprecationHandler deprecationHandler() {
		StandardApiVersionDeprecationHandler deprecationHandler = new StandardApiVersionDeprecationHandler();

		// 例: 1.0.0 は2025/12/01から非推奨、2026/06/30に廃止予定、移行ガイドのリンクを表示
		deprecationHandler.configureVersion("1.0.0")
				.setDeprecationDate(ZonedDateTime.parse("2025-12-01T00:00:00Z")) // RFC 9745: Deprecation ヘッダー
				.setSunsetDate(ZonedDateTime.parse("2026-06-30T00:00:00Z")) // RFC 8594: Sunset ヘッダー
				.setDeprecationLink(URI.create("https://example.com/migrate-1-2"), MediaType.TEXT_HTML) // Link: Deprecationの関連リンク
				.setSunsetLink(URI.create("https://example.com/sunset-1-2"), MediaType.TEXT_HTML); // Link: Sunsetの関連リンク

		return deprecationHandler;
	}
}

レスポンスヘッダーに付与されていることを確認できます。

< HTTP/1.1 200 
< Deprecation: @1764547200
< Link: <https://example.com/migrate-1-2>; rel="deprecation"; type="text/html"
< Sunset: Tue, 30 Jun 2026 00:00:00 GMT
< Link: <https://example.com/sunset-1-2>; rel="sunset"; type="text/html"
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 11
< Date: Wed, 10 Dec 2025 11:17:07 GMT

パスセグメントを利用する例

URIにバージョン指定を含める場合は、パスのどのセグメントがバージョンを示すか設定する必要があります。

JavaConfig

@Configuration
public class WebConfiguration implements WebMvcConfigurer {

	@Override
	public void configureApiVersioning(ApiVersionConfigurer configurer) {
		configurer.usePathSegment(0); // URIの先頭がバージョン
	}
}

Controller

リクエストのURIとして/v1/productsのようなバージョンの指定ができるような設定をしてみます。
セマンティックバージョニングによってv1はバージョン1.0.0として認識されます。
ここでは@RequestMappingのパスの先頭にバージョンを示す/v{version}を付与します。
このバージョンを示すパスをControllerクラスで毎回付与することを省きたい場合はPath Matchingが利用できます。

@RestController
@RequestMapping("/v{version}/products")
class ProductController {

  // 1.0.0 に対応
  @GetMapping(path = "/{id}", version = "1.0.0")
  public ProductV1 getV1(@PathVariable Long id) { /* ... */ }

  // 1.2.0 以降を「ベースライン」でまとめて対応
  @GetMapping(path = "/{id}", version = "1.2.0+")
  public ProductV2 getBaseline(@PathVariable Long id) { /* ... */ }
}

これで下記のようにURIにバージョン指定をしてAPIリクエストができるようになりました。

curl -v localhost:8080/v1/products/1

(補足)ベースライン表記(+)判定について

セマンティックバージョニングの解析を行うSemanticApiVersionParserではバージョンをmajor.minor.patchに分解するだけで、+(ベースライン)の判定は行いません。
ベースラインの判定はVersionRequestCondition が行い、Spring Frameworkがリクエストをマッピングする際に利用されます。

まとめ

解説した機能をまとめなおしてみました。

  • APIバージョン管理を有効化するためには設定が必要
    • デフォルトで対応しているバージョン解決方法は4つ
      • リクエストヘッダー
      • リクエストパラメータ
      • URLパスのセグメント
      • メディアタイプのパラメータ
    • デフォルトバージョンの指定が可能
    • バージョン指定の必須指定が可能
    • 非推奨通知の設定が可能
  • バージョン設定は @RequestMapping / @GetMapping などに version 属性を付与
    • +でベースライン指定も可能
    • セマンティックバージョニング(major.minor.patch)で管理
  • 未指定・未サポートのバージョンはMissingApiVersionException / InvalidApiVersionException が発生

この仕組みを導入することで、既存利用者への影響を最小限に抑えつつ、柔軟な機能拡張が可能になります。
ControllerクラスでAPIバージョンを判別するような処理が不要になったためシンプルに記述できるようになったのがうれしいですね。
今後のSpring Framework 7以上の開発では積極的に取り入れたい機能と感じました。


記載されている会社名、製品名、サービス名は、各社の商標または登録商標です。

9
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?