Spring Boot 4 と Spring Framework 7:高速で安全なマイクロサービスのための Java の新時代
Spring Boot 4.0 が 2025 年 11 月にリリースされた。Spring Framework 7.0 とセットで考えると、これはマイナーアップデートじゃない。モジュラー化、null 安全性、宣言的 HTTP クライアント、ネイティブレジリエンス、エンタープライズ Java の書き方そのものが変わる。
長く Spring を触ってきて、個人的に「やっと来た」と思うポイントがいくつかある。コードと一緒に整理する。
最低要件:モダン性を擁護する
Spring Boot 4 は技術スタックを引き上げた。最低ラインは Java 17 のままだが、正直 Java 21 以上(できれば Java 25)で動かしたい。Virtual Threads が使えるかどうかで、WebFlux を選ぶべきかどうかの判断そのものが変わってくる。
残りの要件を整理しておく:
- Jakarta EE 11 に完全対応。Servlet 6.1、JPA 3.2、Bean Validation 3.1 が入る
- Kotlin は 2.2 以上が必要
- GraalVM 24 以上を完全サポート、ネイティブイメージ生成が最適化されている
パフォーマンスへの大きな飛躍:モジュラー化と AOT
目に見えにくいが、一番大事な改善はコアのモジュラー化だ。
モジュラー化:荷物を解く
今まで spring-boot-autoconfigure は、使わない設定も全部詰め込んだ巨大なモジュールだった。クラスパスにノイズが乗るし、起動も無駄に遅い。
Spring Boot 4 はこれを細かく分割した。必要なものだけロードされる。何が嬉しいかというと、スキャンするコードが減って起動が速くなる。メモリ消費も下がる。IDE の補完が正確になる。関係ないクラスやプロパティが出てこない。GraalVM のネイティブイメージも効率的になる。
AOT コンパイルと GraalVM
GraalVM 24 以上との統合が完了した。AOT(Ahead-of-Time)処理はコンパイル時間の短縮、起動時のメモリフットプリント削減、デッドコード除去の静的解析向上、reflection hints の互換性改善と、全面的に磨かれている。
新しい @ConfigurationPropertiesSource 注釈で、さらに細かくモジュラー化できる。アプリケーションに関連する設定プロパティだけが処理される仕組みだ。
Null 安全性:NullPointerException の終焉
Java で null 扱いが本番バグの温垢だったのは言うまでもない。Spring Framework 7 は製品全体で JSpecify を null 安全性の標準として採用した。個人的に、これが今回一番歓迎したい変更だ。
JSpecify とは何か?
JSpecify は Google、JetBrains、Meta、Oracle が支援する共同標準だ。値が nullable(null になる可能性がある)か non-nullable(null にならない)かを明示する注釈を提供する。
@Nullable は「この値は null になる可能性がある。扱う前に確認が必要」という意味。@NonNull は「この値は null にならない。安心して使える」。そして @NullMarked はパッケージ全体のデフォルトを non-nullable に設定する。
ゲームチェンジャー:@NullMarked
package-info.java に一行書くだけで、パッケージ全体を null 安全にできる:
// src/main/java/com/tuempresa/servicio/package-info.java
@org.jspecify.annotations.NullMarked
package com.tuempresa.servicio;IntelliJ IDEA 2025.3+ なら、コードを書いている最中に警告が出る。nullable な値を non-nullable なコンテキストに渡そうとした瞬間に。
実用的な例を示す:
@NullMarked
package com.ejemplo.usuarios;
public class UsuarioService {
// IDE は null を渡そうとしたときに警告を出す
public Usuario crearUsuario(@NonNull String nombre, @Nullable String apellido) {
// nombre は決して null ではない - 直接安全に使用可能
String nombreCompleto = nombre.toUpperCase();
// apellido は null の可能性がある - 確認が必要
if (apellido != null) {
nombreCompleto += " " + apellido.toUpperCase();
}
return new Usuario(nombreCompleto);
}
}本番で NPE が飛ぶ前に、エディタで気づける。これだけで相当なバグが減るはずだ。
宣言的 HTTP クライアント:Feign の"殺し屋"
Spring Cloud OpenFeign に頼らなくてよくなった。Spring Boot 4 は宣言的 HTTP クライアントを標準で持っている。
RestTemplate とはおさらば
RestTemplate は Spring Framework 7 で非推奨になった。長生きしたが、冗長でテストも書きにくかった。将来的なバージョンで削除される予定なので、今のうちに移行したい。
新時代:RestClient と @HttpExchange
RestClient は RestTemplate のモダンな代替で、流暢な API を持つ。だが本命は @HttpExchange を使った宣言的クライアントだ:
package com.tuempresa.clients;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.PostExchange;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.bind.annotation.PathVariable;
import org.springframework.bind.annotation.RequestBody;
@HttpExchange("/api/productos")
public interface ProductoClient {
@GetExchange("/{id}")
Producto obtenerProducto(@PathVariable("id") String id);
@GetExchange
List<Producto> listarProductos();
@PostExchange
Producto crearProducto(@RequestBody Producto producto);
}@Configuration
public class ClientConfig {
@Bean
public ProductoClient productoClient(RestClient.Builder builder) {
RestClient restClient = builder
.baseUrl("https://api.ejemplo.com")
.build();
HttpServiceProxyFactory factory = HttpServiceProxyFactory
.builderFor(RestClientAdapter.create(restClient))
.build();
return factory.createClient(ProductoClient.class);
}
}何が良いか。ボイラープレートがゼロで、呼び出す内容を定義するだけでいい。インターフェースだからモックも簡単だし、型安全で、追加の依存関係も不要。
それと、clientType 属性で RestClient(デフォルト)と WebClient(リアクティブ)を切り替えられる。クライアントコードを書き換えずに済むのが地味に嬉しい。
レジリエンスと API バージョニング
外部ライブラリに頼っていたパターンが、フレームワークのコアに入ってきた。
ネイティブレジリエンス
Spring Retry プロジェクトにお世話になっていた注釈が、Spring Framework 7 に直接統合された。@Retryable でリトライ、@ConcurrencyLimit で同時呼び出し数の制限、@EnableResilientMethods で bean 上でこれらを有効化できる。
@Service
@EnableResilientMethods
public class PagoService {
@Retryable(maxAttempts = 3, backoff = @Backoff(delay = 1000))
public PagoRespuesta procesarPago(PagoRequest request) {
// 失敗した場合、1 秒間隔で最大 3 回リトライ
return pagoGateway.procesar(request);
}
@ConcurrencyLimit(maxConcurrency = 10)
public Report generarReporte() {
// 最大 10 個の同時呼び出し
return reportService.generar();
}
}ネイティブ API バージョニング
API バージョニングがついにフレームワーク標準で扱える。@RequestMapping に version 属性が追加された:
@RestController
@RequestMapping("/api/pedidos")
public class PedidoController {
// バージョン 1:シンプルなレスポンス
@GetMapping(version = "1", produces = MediaType.APPLICATION_JSON_VALUE)
public List<PedidoV1> getPedidosV1() {
return pedidoService.obtenerPedidosSimples();
}
// バージョン 2:メタデータ付きのリッチレスポンス
@GetMapping(version = "2", produces = MediaType.APPLICATION_JSON_VALUE)
public PedidoResponseV2 getPedidosV2() {
return pedidoService.obtenerPedidosCompletos();
}
}@Configuration
public class ApiVersioningConfig implements WebMvcConfigurer {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
// ヘッダー戦略
configurer.apiVersioning(builder -> builder
.strategy(VersionStrategy.HEADER)
.headerName("API-Version")
);
// またはパス戦略:/v1/pedidos, /v2/pedidos
// またはクエリーパラメータ戦略:/pedidos?version=1
}
}戦略は一箇所で集中管理できる。パス、ヘッダー、クエリーパラメータのどれを使うか、controller を書き直さずに切り替えられる。
高精細オブザーバビリティ
クラウドネイティブアプリでモニタリングは必須だ。Spring Boot 4 はここも手厚くした。
OpenTelemetry との統合
Micrometer 2.0 でパフォーマンスが向上した。ネイティブ OpenTelemetry の公式スターターが含まれる。ログ、メトリクス、トレースが自動的に相関付けられる。
@Observed 注釈
メソッドに付けるだけで、メトリクスとトレースが自動生成される:
@Service
public class InventarioService {
@Observed(name = "inventario.verificar", contextualName = "verificarDisponibilidad")
public boolean verificarDisponibilidad(String productoId) {
// 自動的に以下のメトリクスを生成:
// - 実行時間
// - 呼び出し回数
// - エラーレート
return inventarioRepository.existeStock(productoId);
}
}SSL ヘルスモニタリング
Actuator が SSL 証明書の有効期限を監視するようになった。デフォルトで 14〜30 日前に /actuator/health で警告が出る:
{
"status": "UP",
"components": {
"ssl": {
"status": "WARNING",
"details": {
"certificate": "api.ejemplo.com",
"expiresIn": "12 days",
"expiryDate": "2025-12-05"
}
}
}
}証明書の期限切れで本番が落ちる、という定番の事故を防げる。
コアの変化:Client パターン
Spring を使ったことがあれば JdbcTemplate、RestTemplate、JmsTemplate はおなじみだろう。Spring Framework 7 は "Template Method" パターンから、builders と関数型インターフェースに基づく Client パターンへ移行している。
RestTemplate は非推奨で、RestClient / @HttpExchange が後継。JdbcTemplate と NamedParameterJdbcTemplate は JdbcClient と共存する形。JmsTemplate には新規の JmsClient が追加された。
JdbcClient:よりクリーンなコード
JdbcClient は冗長さを削いだビルダーパターンだ。以前の JdbcTemplate と比べると:
public List<Persona> findAll() {
return jdbcTemplate.query(
"SELECT id, nombre, edad FROM persona",
new RowMapper<Persona>() {
@Override
public Persona mapRow(ResultSet rs, int rowNum) throws SQLException {
Persona p = new Persona();
p.setId(rs.getLong("id"));
p.setNombre(rs.getString("nombre"));
p.setEdad(rs.getInt("edad"));
return p;
}
}
);
}public List<Persona> findAll() {
return jdbcClient.sql("SELECT id, nombre, edad FROM persona")
.query((rs, rowNum) -> new Persona(
rs.getLong("id"),
rs.getString("nombre"),
rs.getInt("edad")
))
.list();
}
// または自動マッピングでもっとシンプル
public List<Persona> findAll() {
return jdbcClient.sql("SELECT * FROM persona")
.query(Persona.class)
.list();
}パラメータ付きクエリも簡潔に書ける:
public Optional<Persona> findById(Long id) {
return jdbcClient.sql("SELECT * FROM persona WHERE id = :id")
.param("id", id)
.query(Persona.class)
.optional();
}書き込み操作も同様だ:
public int actualizarEdad(Long id, int nuevaEdad) {
return jdbcClient.sql("UPDATE persona SET edad = :edad WHERE id = :id")
.param("edad", nuevaEdad)
.param("id", id)
.update();
}簡潔で読みやすい。lambda とメソッド参照が使えるし、Optional と統合されているので null 安全に扱える。AOT と GraalVM のサポートも改善された。
テストの改善
より細かいテストスライス
アプリケーションコンテキスト全体をロードせずに、特定コンポーネントだけテストできる:
@HttpServiceClientTest
class ProductoClientTest {
@Autowired
private ProductoClient productoClient;
@Test
void deberiaObtenerProducto() {
// HTTP クライアントに必要な設定だけをロード
Producto producto = productoClient.obtenerProducto("123");
assertThat(producto).isNotNull();
}
}改良された TestRestTemplate
RestClient との統合が向上し、型指定されたレスポンスのサポートも改善された:
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class IntegrationTest {
@Autowired
private TestRestClient restClient;
@Test
void deberiaCrearUsuario() {
Usuario nuevoUsuario = new Usuario("Juan", "Pérez");
Usuario creado = restClient.post()
.uri("/api/usuarios")
.body(nuevoUsuario)
.exchange()
.expectStatus().isCreated()
.expectBody(Usuario.class)
.returnResult();
assertThat(creado.getId()).isNotNull();
}
}Spring Boot 3 からの移行
強制的な変更
- Jackson 3.x(2.x からの更新)
- Jakarta 名前空間の完全移行(
javax.*→jakarta.*) - Java 17 最低要件(Java 21+ を推奨)
- Kotlin 2.2+(使っている場合)
重要な非推奨
RestTemplateはRestClientまたは宣言的クライアントへ- Spring Security で lambda 設定が必須
- 一部の設定プロパティがリネームされている
移行ツール
OpenRewrite で大部分を自動化できる:
<plugin>
<groupId>org.openrewrite.maven</groupId>
<artifactId>rewrite-maven-plugin</artifactId>
<version>5.42.0</version>
<configuration>
<activeRecipes>
<recipe>org.openrewrite.java.spring.boot4.UpgradeSpringBoot_4_0</recipe>
</activeRecipes>
</configuration>
</plugin>依存関係の更新、javax → jakarta 変更、非推奨 API の移行、設定ファイルの更新を自動でやってくれる。
Spring Boot 4 に移行する理由
Spring Boot 4 と Spring Framework 7 は、Java アプリを軽く、速く、安全にする。モジュラー化と AOT で起動時間とメモリが改善される。JSpecify で本番 NPE が減る。宣言的 API でボイラープレートが減る。レジリエンスが標準で入る。オブザーバビリティが一流になる。コンテナとクラウドに最適化される。
移行は些細じゃない。依存関係の更新と設定の調整が必要だ。だが、得られる生産性、パフォーマンス、保守性を考えれば、すぐに元は取れる。
これは今後 10 年のエンタープライズ Java の基盤だ。未来に向けてアプリを構築しているなら、これが構築すべきプラットフォームになる。