14 분 소요


Spring Boot 4.0 Key Changes and New Features

Spring Boot 4.0 was released in November 2025, built on Spring Framework 7.0. This major release brings significant improvements including first-class API versioning, enhanced Kotlin support, modular auto-configuration, and full Jakarta EE 11 alignment. This post covers the key changes, new features, and migration considerations.

1. Minimum Requirements

Spring Boot 4.0 raises the baseline requirements:

Component Minimum Version Recommended
Java 17 (LTS) 25
Kotlin 2.2 2.2.20+
Gradle 8.14+ 9.x
Maven 3.9+ -

Key points:

  • Java 17 remains the minimum to maintain broad industry compatibility
  • Java 25 (September 2025) is fully supported with all its features
  • Kotlin 2.2 baseline with K2 compiler support
  • GraalVM 24 alignment for native image builds

2. Major Dependency Upgrades

Spring Portfolio

Dependency Version
Spring Framework 7.0
Spring Security 7.0
Spring Data 2025.1
Spring Integration 7.0
Spring Session 4.0
Spring Batch 6.0
Spring AMQP 4.0
Spring for Apache Kafka 4.0

Third-Party Libraries

Library Version
Hibernate 7.1
Jackson 3.0
Tomcat 11.0
H2 Database 2.4
MongoDB Driver 5.6.0

Jakarta EE 11 Alignment

Spring Boot 4.0 fully adopts Jakarta EE 11:

  • Jakarta Servlet 6.1 - Modern web API support
  • Jakarta Persistence 3.2 - Enhanced JPA features
  • Jakarta Validation 3.1 - Updated Bean Validation
  • Jakarta WebSocket 2.2 - WebSocket improvements

Note: Undertow is not supported because it is not yet compatible with Servlet 6.1. Use Tomcat or Jetty instead.

3. New Features

3.1 HTTP Service Clients

Spring Boot 4.0 provides first-class support for declarative HTTP clients. Define an interface, annotate it, and Spring generates the implementation at runtime.

@HttpExchange("/api/users")
public interface UserClient {

    @GetExchange("/{id}")
    User getUser(@PathVariable("id") Long id);

    @GetExchange
    List<User> getAllUsers();

    @PostExchange
    User createUser(@RequestBody User user);

    @DeleteExchange("/{id}")
    void deleteUser(@PathVariable("id") Long id);
}

Configure the client using @ImportHttpServices:

@Configuration
@ImportHttpServices(group = "user-service", types = UserClient.class)
public class HttpClientConfig {

    @Bean
    public RestClientHttpServiceGroupConfigurer configurer() {
        return groups -> groups
            .filterByName("user-service")
            .forEachClient((group, builder) -> {
                builder.baseUrl("https://api.example.com");
                builder.defaultHeader("Accept", "application/json");
            });
    }
}

Use the client by injecting it:

@Service
@RequiredArgsConstructor
public class UserService {

    private final UserClient userClient;

    public User findUser(Long id) {
        return userClient.getUser(id);
    }
}

Benefits:

  • No additional dependencies (part of spring-web)
  • Virtual Threads ready for efficient blocking operations
  • Replaces the need for OpenFeign in many cases

3.2 API Versioning

Spring Boot 4.0 introduces built-in API versioning support. Configure via application.yml:

spring:
  mvc:
    apiversion:
      supported: 1.0, 2.0, 3.0
      default: 1.0
      use:
        # Choose ONE strategy:
        # Path segment: /api/v1/users
        path-segment: 1
        # OR Header: X-API-Version: 1.0
        # header: X-API-Version
        # OR Query param: ?version=1.0
        # query-parameter: version

Use versioning in controllers:

@RestController
@RequestMapping("/api/users")
public class UserController {

    // Available in v1.0 only
    @GetExchange(value = "/{id}", version = "1.0")
    public UserV1Response getUserV1(@PathVariable Long id) {
        return userService.getUserV1(id);
    }

    // Available in v2.0 and later (baseline version)
    @GetExchange(value = "/{id}", version = "2.0+")
    public UserV2Response getUserV2(@PathVariable Long id) {
        return userService.getUserV2(id);
    }
}

For programmatic configuration:

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureApiVersioning(ApiVersionConfigurer configurer) {
        configurer
            .useRequestHeader("API-Version")
            .supportedVersions("1.0", "2.0", "3.0")
            .defaultVersion("2.0");
    }
}

3.3 OpenTelemetry Starter

A new spring-boot-starter-opentelemetry module simplifies observability setup:

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-opentelemetry'
}
management:
  opentelemetry:
    tracing:
      export:
        otlp:
          enabled: true
          endpoint: http://localhost:4317
  logging:
    export:
      otlp:
        enabled: true

3.4 JmsClient Support

Spring Boot 4.0 introduces JmsClient as a modern alternative to JmsTemplate:

@Service
@RequiredArgsConstructor
public class MessageService {

    private final JmsClient jmsClient;

    public void sendMessage(String destination, OrderEvent event) {
        jmsClient.send(destination, event);
    }

    public OrderEvent receiveMessage(String destination) {
        return jmsClient.receive(destination, OrderEvent.class);
    }
}

3.5 Virtual Threading Support

Virtual threads are fully supported when running on Java 21+:

spring:
  threads:
    virtual:
      enabled: true

This enables efficient handling of blocking I/O operations without the complexity of reactive programming.

4. Kotlin Support Improvements

4.1 Kotlin 2.2 Baseline with K2 Compiler

Spring Boot 4.0 requires Kotlin 2.2+, which includes:

  • K2 compiler for faster compilation
  • Improved code analysis
  • Better IDE support

4.2 JSpecify Null-Safety

JSpecify annotations provide standardized null-safety across the Spring portfolio. Kotlin 2.x automatically translates these to Kotlin nullability:

// Spring APIs now have proper Kotlin nullability
// No more platform types!
val user: User = userRepository.findById(id) // Returns User, not User!
val maybeUser: User? = userRepository.findByEmail(email) // Nullable

4.3 Coroutines Context Propagation

Automatic context propagation for observability in coroutines:

spring:
  reactor:
    context-propagation: auto
@Service
class UserService(private val userRepository: UserRepository) {

    // Tracing context automatically propagated
    suspend fun findUser(id: Long): User {
        return userRepository.findById(id)
    }
}

4.4 Kotlin Serialization Module

New spring-boot-starter-kotlin-serialization for kotlinx.serialization support:

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-kotlin-serialization'
}
@Serializable
data class User(
    val id: Long,
    val name: String,
    val email: String
)

4.5 BeanRegistrarDsl

Programmatic bean registration with Kotlin DSL:

class MyBeanRegistrar : BeanRegistrarDsl({
    // Simple registration
    registerBean<UserRepository>()

    // Registration with options
    registerBean(
        name = "customService",
        prototype = true,
        lazyInit = true,
        description = "Custom service bean"
    ) {
        CustomService(ref<UserRepository>())
    }

    // Profile-specific registration
    profile("production") {
        registerBean { ProductionDataSource() }
    }

    profile("development") {
        registerBean { DevelopmentDataSource() }
    }
})

Import in configuration:

@Configuration
@Import(MyBeanRegistrar::class)
class AppConfig

5. Configuration Changes

Renamed Properties

Old Property New Property
management.tracing.enabled management.tracing.export.enabled
spring.dao.exceptiontranslation.enabled spring.persistence.exceptiontranslation.enabled

New Properties

Property Default Description
logging.console.enabled true Enable/disable console logging
management.tracing.export.enabled true Enable tracing export
spring.threads.virtual.enabled false Enable virtual threads

Example Configuration

spring:
  threads:
    virtual:
      enabled: true
  persistence:
    exceptiontranslation:
      enabled: true

management:
  tracing:
    export:
      enabled: true

logging:
  console:
    enabled: true
  level:
    root: INFO
    com.example: DEBUG

6. Code Examples

Complete HTTP Service Client Example

// Define the interface
@HttpExchange("/api/v1/products")
public interface ProductClient {

    @GetExchange
    List<Product> findAll();

    @GetExchange("/{id}")
    Product findById(@PathVariable Long id);

    @GetExchange("/search")
    List<Product> search(@RequestParam String name,
                         @RequestParam(required = false) String category);

    @PostExchange
    Product create(@RequestBody CreateProductRequest request);

    @PutExchange("/{id}")
    Product update(@PathVariable Long id, @RequestBody UpdateProductRequest request);

    @DeleteExchange("/{id}")
    void delete(@PathVariable Long id);
}

// Configuration
@Configuration
@ImportHttpServices(group = "products", types = ProductClient.class)
public class ProductClientConfig {

    @Bean
    public RestClientHttpServiceGroupConfigurer productClientConfigurer() {
        return groups -> groups
            .filterByName("products")
            .forEachClient((group, builder) -> {
                builder.baseUrl("https://api.store.example.com");
                builder.defaultHeader("Authorization", "Bearer " + getToken());
                builder.defaultHeader("Content-Type", "application/json");
            });
    }

    private String getToken() {
        // Token retrieval logic
        return "your-api-token";
    }
}

// Usage
@RestController
@RequestMapping("/products")
@RequiredArgsConstructor
public class ProductController {

    private final ProductClient productClient;

    @GetMapping
    public List<Product> getProducts() {
        return productClient.findAll();
    }

    @GetMapping("/{id}")
    public Product getProduct(@PathVariable Long id) {
        return productClient.findById(id);
    }
}

API Versioning Example

@RestController
@RequestMapping("/api/orders")
public class OrderController {

    private final OrderService orderService;

    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }

    // V1: Basic order response
    @GetMapping(value = "/{id}", version = "1.0")
    public OrderResponseV1 getOrderV1(@PathVariable Long id) {
        Order order = orderService.findById(id);
        return new OrderResponseV1(
            order.getId(),
            order.getStatus(),
            order.getTotal()
        );
    }

    // V2+: Extended response with items
    @GetMapping(value = "/{id}", version = "2.0+")
    public OrderResponseV2 getOrderV2(@PathVariable Long id) {
        Order order = orderService.findById(id);
        return new OrderResponseV2(
            order.getId(),
            order.getStatus(),
            order.getTotal(),
            order.getItems(),
            order.getCreatedAt(),
            order.getUpdatedAt()
        );
    }
}

// Response DTOs
public record OrderResponseV1(Long id, String status, BigDecimal total) {}

public record OrderResponseV2(
    Long id,
    String status,
    BigDecimal total,
    List<OrderItem> items,
    Instant createdAt,
    Instant updatedAt
) {}

Kotlin BeanRegistrar Example

// Bean classes
class UserRepository(private val dataSource: DataSource)

class UserService(
    private val userRepository: UserRepository,
    private val emailService: EmailService
)

class EmailService(private val mailSender: MailSender)

// Bean registrar with DSL
class ApplicationBeanRegistrar : BeanRegistrarDsl({

    // Infrastructure beans
    registerBean<DataSource>(infrastructure = true) {
        HikariDataSource().apply {
            jdbcUrl = env.getProperty("spring.datasource.url")
            username = env.getProperty("spring.datasource.username")
            password = env.getProperty("spring.datasource.password")
        }
    }

    // Repository layer
    registerBean { UserRepository(ref()) }

    // Service layer with multiple dependencies
    registerBean {
        UserService(
            userRepository = ref(),
            emailService = ref()
        )
    }

    // Conditional registration
    profile("!test") {
        registerBean<MailSender> { SmtpMailSender() }
        registerBean { EmailService(ref()) }
    }

    profile("test") {
        registerBean<MailSender> { MockMailSender() }
        registerBean { EmailService(ref()) }
    }

    // Prototype scoped bean
    registerBean(prototype = true) {
        RequestScopedService(ref())
    }
})

// Configuration
@Configuration
@Import(ApplicationBeanRegistrar::class)
class AppConfiguration

7. Migration Guide

Upgrade Path

  1. Upgrade to Spring Boot 3.5 first if you are on an earlier version
  2. Update Java version to at least 17 (25 recommended)
  3. Update Kotlin version to 2.2+ if using Kotlin
  4. Update Gradle to 8.14+ or 9.x
  5. Run the OpenRewrite migration recipe
plugins {
    id 'org.openrewrite.rewrite' version '7.x.x'
}

dependencies {
    rewrite 'org.openrewrite.recipe:rewrite-spring:latest.release'
}

// Run: gradle rewriteRun

Breaking Changes

  1. JUnit 4 Removed: Only JUnit Jupiter 6 is supported
  2. Undertow Not Supported: Use Tomcat or Jetty
  3. RestTemplate Deprecation Warning: RestClient is the recommended alternative (RestTemplate will be removed in Spring Framework 8)
  4. Hibernate 7.1 Changes: Detached entities cannot be reassociated with a persistence context
  5. Jackson 3.0: Some API changes from Jackson 2.x

Property Migration

Update these properties in your application.yml:

# Before (Spring Boot 3.x)
management:
  tracing:
    enabled: true
spring:
  dao:
    exceptiontranslation:
      enabled: true

# After (Spring Boot 4.0)
management:
  tracing:
    export:
      enabled: true
spring:
  persistence:
    exceptiontranslation:
      enabled: true

Support Timeline

Version OSS Support Until
Spring Boot 4.0 November 2026
Spring Boot 3.5 June 2026
Spring Framework 7.0 November 2026
Spring Framework 6.2 June 2026

8. Conclusion

Spring Boot 4.0 brings substantial improvements for modern Java and Kotlin development. The declarative HTTP clients, built-in API versioning, and enhanced Kotlin support simplify common development tasks. The full Jakarta EE 11 alignment and JSpecify null-safety annotations provide a solid foundation for building robust applications.

For production migrations, start by upgrading to Spring Boot 3.5, validate your application, then proceed to 4.0. Use the OpenRewrite recipes to automate property and API migrations. Take advantage of virtual threads on Java 21+ for improved scalability without reactive complexity.

Spring Boot 4.0 주요 변경사항과 새로운 기능

Spring Boot 4.0이 2025년 11월에 출시되었으며, Spring Framework 7.0을 기반으로 한다. 이번 메이저 릴리스는 일급 API 버저닝, 향상된 Kotlin 지원, 모듈화된 자동 구성, Jakarta EE 11 완전 호환 등 중요한 개선사항을 포함한다. 이 글에서는 주요 변경사항, 새로운 기능, 마이그레이션 고려사항을 다룬다.

1. 최소 요구사항

Spring Boot 4.0은 기준 요구사항을 상향한다:

구성요소 최소 버전 권장 버전
Java 17 (LTS) 25
Kotlin 2.2 2.2.20+
Gradle 8.14+ 9.x
Maven 3.9+ -

핵심 사항:

  • Java 17은 광범위한 산업 호환성을 위해 최소 버전으로 유지
  • Java 25 (2025년 9월)의 모든 기능 완전 지원
  • Kotlin 2.2 기준선과 K2 컴파일러 지원
  • GraalVM 24 네이티브 이미지 빌드 호환

2. 주요 의존성 업그레이드

Spring 포트폴리오

의존성 버전
Spring Framework 7.0
Spring Security 7.0
Spring Data 2025.1
Spring Integration 7.0
Spring Session 4.0
Spring Batch 6.0
Spring AMQP 4.0
Spring for Apache Kafka 4.0

서드파티 라이브러리

라이브러리 버전
Hibernate 7.1
Jackson 3.0
Tomcat 11.0
H2 Database 2.4
MongoDB Driver 5.6.0

Jakarta EE 11 호환

Spring Boot 4.0은 Jakarta EE 11을 완전히 채택한다:

  • Jakarta Servlet 6.1 - 최신 웹 API 지원
  • Jakarta Persistence 3.2 - 향상된 JPA 기능
  • Jakarta Validation 3.1 - 업데이트된 Bean Validation
  • Jakarta WebSocket 2.2 - WebSocket 개선

참고: Undertow는 Servlet 6.1과 아직 호환되지 않아 지원되지 않는다. Tomcat 또는 Jetty를 사용해야 한다.

3. 새로운 기능

3.1 HTTP Service Client

Spring Boot 4.0은 선언적 HTTP 클라이언트를 일급으로 지원한다. 인터페이스를 정의하고 어노테이션을 붙이면 Spring이 런타임에 구현체를 생성한다.

@HttpExchange("/api/users")
public interface UserClient {

    @GetExchange("/{id}")
    User getUser(@PathVariable("id") Long id);

    @GetExchange
    List<User> getAllUsers();

    @PostExchange
    User createUser(@RequestBody User user);

    @DeleteExchange("/{id}")
    void deleteUser(@PathVariable("id") Long id);
}

@ImportHttpServices를 사용하여 클라이언트를 구성한다:

@Configuration
@ImportHttpServices(group = "user-service", types = UserClient.class)
public class HttpClientConfig {

    @Bean
    public RestClientHttpServiceGroupConfigurer configurer() {
        return groups -> groups
            .filterByName("user-service")
            .forEachClient((group, builder) -> {
                builder.baseUrl("https://api.example.com");
                builder.defaultHeader("Accept", "application/json");
            });
    }
}

클라이언트 주입하여 사용:

@Service
@RequiredArgsConstructor
public class UserService {

    private final UserClient userClient;

    public User findUser(Long id) {
        return userClient.getUser(id);
    }
}

장점:

  • 추가 의존성 불필요 (spring-web에 포함)
  • Virtual Thread 지원으로 효율적인 블로킹 작업
  • 많은 경우 OpenFeign을 대체 가능

3.2 API 버저닝

Spring Boot 4.0은 내장 API 버저닝 지원을 도입한다. application.yml로 구성:

spring:
  mvc:
    apiversion:
      supported: 1.0, 2.0, 3.0
      default: 1.0
      use:
        # 전략 중 하나 선택:
        # Path segment: /api/v1/users
        path-segment: 1
        # 또는 Header: X-API-Version: 1.0
        # header: X-API-Version
        # 또는 Query param: ?version=1.0
        # query-parameter: version

컨트롤러에서 버저닝 사용:

@RestController
@RequestMapping("/api/users")
public class UserController {

    // v1.0에서만 사용 가능
    @GetExchange(value = "/{id}", version = "1.0")
    public UserV1Response getUserV1(@PathVariable Long id) {
        return userService.getUserV1(id);
    }

    // v2.0 이상에서 사용 가능 (baseline version)
    @GetExchange(value = "/{id}", version = "2.0+")
    public UserV2Response getUserV2(@PathVariable Long id) {
        return userService.getUserV2(id);
    }
}

프로그래밍 방식 구성:

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureApiVersioning(ApiVersionConfigurer configurer) {
        configurer
            .useRequestHeader("API-Version")
            .supportedVersions("1.0", "2.0", "3.0")
            .defaultVersion("2.0");
    }
}

3.3 OpenTelemetry Starter

새로운 spring-boot-starter-opentelemetry 모듈로 관측성 설정을 간소화한다:

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-opentelemetry'
}
management:
  opentelemetry:
    tracing:
      export:
        otlp:
          enabled: true
          endpoint: http://localhost:4317
  logging:
    export:
      otlp:
        enabled: true

3.4 JmsClient 지원

Spring Boot 4.0은 JmsTemplate의 현대적 대안으로 JmsClient를 도입한다:

@Service
@RequiredArgsConstructor
public class MessageService {

    private final JmsClient jmsClient;

    public void sendMessage(String destination, OrderEvent event) {
        jmsClient.send(destination, event);
    }

    public OrderEvent receiveMessage(String destination) {
        return jmsClient.receive(destination, OrderEvent.class);
    }
}

3.5 Virtual Threading 지원

Java 21+ 실행 시 가상 스레드가 완전 지원된다:

spring:
  threads:
    virtual:
      enabled: true

리액티브 프로그래밍의 복잡성 없이 효율적인 블로킹 I/O 작업 처리가 가능하다.

4. Kotlin 지원 개선

4.1 Kotlin 2.2 기준선과 K2 컴파일러

Spring Boot 4.0은 Kotlin 2.2+를 요구하며, 다음을 포함한다:

  • K2 컴파일러로 더 빠른 컴파일
  • 향상된 코드 분석
  • 더 나은 IDE 지원

4.2 JSpecify Null-Safety

JSpecify 어노테이션은 Spring 포트폴리오 전반에 걸쳐 표준화된 null-safety를 제공한다. Kotlin 2.x는 이를 자동으로 Kotlin nullability로 변환한다:

// Spring API가 이제 적절한 Kotlin nullability를 가짐
// 더 이상 플랫폼 타입이 없음!
val user: User = userRepository.findById(id) // User 반환, User!가 아님
val maybeUser: User? = userRepository.findByEmail(email) // Nullable

4.3 코루틴 컨텍스트 전파

코루틴에서 관측성을 위한 자동 컨텍스트 전파:

spring:
  reactor:
    context-propagation: auto
@Service
class UserService(private val userRepository: UserRepository) {

    // 트레이싱 컨텍스트 자동 전파
    suspend fun findUser(id: Long): User {
        return userRepository.findById(id)
    }
}

4.4 Kotlin Serialization 모듈

kotlinx.serialization 지원을 위한 새로운 spring-boot-starter-kotlin-serialization:

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-kotlin-serialization'
}
@Serializable
data class User(
    val id: Long,
    val name: String,
    val email: String
)

4.5 BeanRegistrarDsl

Kotlin DSL을 사용한 프로그래밍 방식의 빈 등록:

class MyBeanRegistrar : BeanRegistrarDsl({
    // 간단한 등록
    registerBean<UserRepository>()

    // 옵션과 함께 등록
    registerBean(
        name = "customService",
        prototype = true,
        lazyInit = true,
        description = "커스텀 서비스 빈"
    ) {
        CustomService(ref<UserRepository>())
    }

    // 프로파일 별 등록
    profile("production") {
        registerBean { ProductionDataSource() }
    }

    profile("development") {
        registerBean { DevelopmentDataSource() }
    }
})

구성에서 임포트:

@Configuration
@Import(MyBeanRegistrar::class)
class AppConfig

5. 구성 변경사항

이름이 변경된 속성

이전 속성 새 속성
management.tracing.enabled management.tracing.export.enabled
spring.dao.exceptiontranslation.enabled spring.persistence.exceptiontranslation.enabled

새로운 속성

속성 기본값 설명
logging.console.enabled true 콘솔 로깅 활성화/비활성화
management.tracing.export.enabled true 트레이싱 익스포트 활성화
spring.threads.virtual.enabled false 가상 스레드 활성화

예시 구성

spring:
  threads:
    virtual:
      enabled: true
  persistence:
    exceptiontranslation:
      enabled: true

management:
  tracing:
    export:
      enabled: true

logging:
  console:
    enabled: true
  level:
    root: INFO
    com.example: DEBUG

6. 코드 예제

완전한 HTTP Service Client 예제

// 인터페이스 정의
@HttpExchange("/api/v1/products")
public interface ProductClient {

    @GetExchange
    List<Product> findAll();

    @GetExchange("/{id}")
    Product findById(@PathVariable Long id);

    @GetExchange("/search")
    List<Product> search(@RequestParam String name,
                         @RequestParam(required = false) String category);

    @PostExchange
    Product create(@RequestBody CreateProductRequest request);

    @PutExchange("/{id}")
    Product update(@PathVariable Long id, @RequestBody UpdateProductRequest request);

    @DeleteExchange("/{id}")
    void delete(@PathVariable Long id);
}

// 구성
@Configuration
@ImportHttpServices(group = "products", types = ProductClient.class)
public class ProductClientConfig {

    @Bean
    public RestClientHttpServiceGroupConfigurer productClientConfigurer() {
        return groups -> groups
            .filterByName("products")
            .forEachClient((group, builder) -> {
                builder.baseUrl("https://api.store.example.com");
                builder.defaultHeader("Authorization", "Bearer " + getToken());
                builder.defaultHeader("Content-Type", "application/json");
            });
    }

    private String getToken() {
        // 토큰 조회 로직
        return "your-api-token";
    }
}

// 사용
@RestController
@RequestMapping("/products")
@RequiredArgsConstructor
public class ProductController {

    private final ProductClient productClient;

    @GetMapping
    public List<Product> getProducts() {
        return productClient.findAll();
    }

    @GetMapping("/{id}")
    public Product getProduct(@PathVariable Long id) {
        return productClient.findById(id);
    }
}

API 버저닝 예제

@RestController
@RequestMapping("/api/orders")
public class OrderController {

    private final OrderService orderService;

    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }

    // V1: 기본 주문 응답
    @GetMapping(value = "/{id}", version = "1.0")
    public OrderResponseV1 getOrderV1(@PathVariable Long id) {
        Order order = orderService.findById(id);
        return new OrderResponseV1(
            order.getId(),
            order.getStatus(),
            order.getTotal()
        );
    }

    // V2+: 아이템이 포함된 확장 응답
    @GetMapping(value = "/{id}", version = "2.0+")
    public OrderResponseV2 getOrderV2(@PathVariable Long id) {
        Order order = orderService.findById(id);
        return new OrderResponseV2(
            order.getId(),
            order.getStatus(),
            order.getTotal(),
            order.getItems(),
            order.getCreatedAt(),
            order.getUpdatedAt()
        );
    }
}

// 응답 DTO
public record OrderResponseV1(Long id, String status, BigDecimal total) {}

public record OrderResponseV2(
    Long id,
    String status,
    BigDecimal total,
    List<OrderItem> items,
    Instant createdAt,
    Instant updatedAt
) {}

Kotlin BeanRegistrar 예제

// 빈 클래스들
class UserRepository(private val dataSource: DataSource)

class UserService(
    private val userRepository: UserRepository,
    private val emailService: EmailService
)

class EmailService(private val mailSender: MailSender)

// DSL을 사용한 빈 등록자
class ApplicationBeanRegistrar : BeanRegistrarDsl({

    // 인프라 빈
    registerBean<DataSource>(infrastructure = true) {
        HikariDataSource().apply {
            jdbcUrl = env.getProperty("spring.datasource.url")
            username = env.getProperty("spring.datasource.username")
            password = env.getProperty("spring.datasource.password")
        }
    }

    // 리포지토리 레이어
    registerBean { UserRepository(ref()) }

    // 여러 의존성을 가진 서비스 레이어
    registerBean {
        UserService(
            userRepository = ref(),
            emailService = ref()
        )
    }

    // 조건부 등록
    profile("!test") {
        registerBean<MailSender> { SmtpMailSender() }
        registerBean { EmailService(ref()) }
    }

    profile("test") {
        registerBean<MailSender> { MockMailSender() }
        registerBean { EmailService(ref()) }
    }

    // 프로토타입 스코프 빈
    registerBean(prototype = true) {
        RequestScopedService(ref())
    }
})

// 구성
@Configuration
@Import(ApplicationBeanRegistrar::class)
class AppConfiguration

7. 마이그레이션 가이드

업그레이드 경로

  1. 이전 버전이라면 먼저 Spring Boot 3.5로 업그레이드
  2. Java 버전을 17 이상으로 업데이트 (25 권장)
  3. Kotlin 사용 시 Kotlin 버전을 2.2+로 업데이트
  4. Gradle을 8.14+ 또는 9.x로 업데이트
  5. OpenRewrite 마이그레이션 레시피 실행
plugins {
    id 'org.openrewrite.rewrite' version '7.x.x'
}

dependencies {
    rewrite 'org.openrewrite.recipe:rewrite-spring:latest.release'
}

// 실행: gradle rewriteRun

주요 변경사항

  1. JUnit 4 제거: JUnit Jupiter 6만 지원
  2. Undertow 미지원: Tomcat 또는 Jetty 사용
  3. RestTemplate 지원 중단 경고: RestClient가 권장 대안 (RestTemplate은 Spring Framework 8에서 제거 예정)
  4. Hibernate 7.1 변경사항: 분리된 엔티티를 영속성 컨텍스트에 다시 연결할 수 없음
  5. Jackson 3.0: Jackson 2.x에서 일부 API 변경

속성 마이그레이션

application.yml에서 이 속성들을 업데이트:

# 이전 (Spring Boot 3.x)
management:
  tracing:
    enabled: true
spring:
  dao:
    exceptiontranslation:
      enabled: true

# 이후 (Spring Boot 4.0)
management:
  tracing:
    export:
      enabled: true
spring:
  persistence:
    exceptiontranslation:
      enabled: true

지원 일정

버전 OSS 지원 종료
Spring Boot 4.0 2026년 11월
Spring Boot 3.5 2026년 6월
Spring Framework 7.0 2026년 11월
Spring Framework 6.2 2026년 6월

8. 결론

Spring Boot 4.0은 현대적인 Java와 Kotlin 개발을 위한 상당한 개선을 가져온다. 선언적 HTTP 클라이언트, 내장 API 버저닝, 향상된 Kotlin 지원은 일반적인 개발 작업을 단순화한다. 완전한 Jakarta EE 11 호환과 JSpecify null-safety 어노테이션은 견고한 애플리케이션 구축을 위한 단단한 기반을 제공한다.

프로덕션 마이그레이션의 경우, Spring Boot 3.5로 먼저 업그레이드하고, 애플리케이션을 검증한 후 4.0으로 진행하라. OpenRewrite 레시피를 사용하여 속성 및 API 마이그레이션을 자동화하라. Java 21+에서 가상 스레드를 활용하여 리액티브의 복잡성 없이 확장성을 개선하라.

댓글남기기