Zademy

Spring Boot 4 と Spring Framework 7:高速で安全なマイクロサービスのための Java の新時代

Java
SpringBoot; Java
words 単語

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 からの移行

強制的な変更

  1. Jackson 3.x(2.x からの更新)
  2. Jakarta 名前空間の完全移行(javax.* → jakarta.*)
  3. Java 17 最低要件(Java 21+ を推奨)
  4. 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 の基盤だ。未来に向けてアプリを構築しているなら、これが構築すべきプラットフォームになる。