Spring Boot 3.x로 겨우 마이그레이션을 끝냈는데, 또 메이저 버전이 올라간다고요?

Boot 2.x에서 3.x로 넘어갈 때 javax.*jakarta.* 패키지 변경으로 고생했던 분이 많을 겁니다. Boot 4.0은 그때만큼 극적이진 않지만, Jakarta EE 11, Jackson 3.0, Hibernate 7.1 같은 굵직한 변경이 포함되어 있어서 미리 준비하지 않으면 빌드가 깨질 수 있습니다.

이 글에서는 Boot 4.0의 핵심 변경점을 정리하고, 실제 마이그레이션할 때 어떤 순서로 작업하면 좋을지 정리해 봤습니다.

Spring Boot 4.0이 중요한 이유

Boot 4.0은 Spring Framework 7 을 기반으로 합니다. 단순 버전 업이 아니라, 스프링 생태계의 근간이 되는 의존성들이 한꺼번에 올라가는 거라 영향 범위가 넓습니다.

항목Boot 3.xBoot 4.0
Spring Framework6.x7.x
Java 최소 버전1721
Jakarta EE1011
Hibernate6.x7.1
Jackson2.x3.0

왜 이렇게 한꺼번에 올리는 걸까요? 메이저 버전을 올릴 때가 아니면 하위 호환을 깨는 변경을 넣기 어렵기 때문입니다. 그래서 메이저 버전에 모아서 한 번에 정리하는 거죠.

1. Java 21 최소 요구

Boot 3.x는 Java 17이 최소였지만, 4.0은 Java 21 이상 을 요구합니다.

Java 21에서 추가된 기능들이 프레임워크 내부에서 적극 활용됩니다:

  • Virtual Threads — 가벼운 스레드로 동시성 처리
  • Record Patterns — 패턴 매칭 강화
  • Sequenced Collections — 순서가 있는 컬렉션 인터페이스
  • Switch Pattern Matching — switch 문에서 타입 패턴 사용
JAVA
// Java 21의 Virtual Threads — Boot 4.0에서 기본 지원
// 별도 설정 없이도 활용 가능
@Bean
public TomcatProtocolHandlerCustomizer<?> protocolHandler() {
    return handler -> handler.setExecutor(
        Executors.newVirtualThreadPerTaskExecutor()
    );
}

2. Jakarta EE 11 네임스페이스 변경

Boot 3.x에서 javax.*jakarta.* 변경을 겪었다면, 이번엔 그 정도는 아닙니다. 패키지 이름 자체가 바뀌진 않고, 일부 API가 업데이트되거나 제거 됩니다.

주요 변경점

Jakarta EE 11에서 주목할 API 변경:

  • Servlet 6.1: 비동기 처리 API 개선
  • JPA 3.2: 새로운 쿼리 기능, EntityManagerFactory 개선
  • Bean Validation 3.1: 새로운 제약 조건 추가
  • CDI 4.1: 컨텍스트와 의존성 주입 개선

3. Jackson 3.0 — 패키지명이 바뀝니다

이건 꽤 영향이 큽니다. Jackson 3.0은 ** 패키지명이 변경 **되기 때문에 import문을 전부 수정해야 합니다.

JAVA
// Before — Boot 3.x (Jackson 2.x)
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;

// After — Boot 4.0 (Jackson 3.0)
import tools.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.annotation.JsonProperty;  // 어노테이션은 유지
import tools.jackson.core.JsonProcessingException;

어노테이션 패키지(com.fasterxml.jackson.annotation)는 그대로인데, 핵심 패키지(databind, core)는 tools.jackson으로 바뀝니다. IDE의 일괄 치환 기능을 활용하되, 어노테이션 패키지는 건드리지 마세요.

Jackson 3.0의 다른 변경점도 있습니다:

JAVA
// Jackson 3.0에서 ObjectMapper 생성 방식 변경
// Before — 기본 생성자 사용
ObjectMapper mapper = new ObjectMapper();

// After — Builder 패턴 권장
ObjectMapper mapper = JsonMapper.builder()
    .enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
    .build();

4. Hibernate 7.1

Hibernate도 메이저 버전이 올라가면서 몇 가지 중요한 변경이 있습니다.

JAVA
// 1. 기존 Criteria API 일부 제거 — JPA 표준으로 통합
// Before (제거됨!)
Session session = entityManager.unwrap(Session.class);
Criteria criteria = session.createCriteria(User.class);

// After (JPA 표준 Criteria)
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
query.select(query.from(User.class));

// 2. @Type 어노테이션 변경
// Before — @Type(type = "json")
// After — @JdbcTypeCode(SqlTypes.JSON)  // 타입 안전한 방식

5. HTTP Interface Client 개선

Boot 3.2에서 도입된 HTTP Interface Client가 4.0에서 더 강력해졌습니다.

JAVA
// HTTP Interface Client — Boot 4.0 개선 사항
public interface UserClient {

    // 기존 기능 — 선언적 HTTP 클라이언트
    @GetExchange("/users/{id}")
    User getUser(@PathVariable Long id);

    // Boot 4.0 — 에러 핸들링 개선
    @GetExchange("/users/{id}")
    ResponseEntity<User> getUserWithStatus(@PathVariable Long id);

    // Boot 4.0 — 리액티브 지원 강화
    @GetExchange("/users")
    Flux<User> getAllUsers();

    // Boot 4.0 — 멀티파트 요청 지원
    @PostExchange("/users/{id}/avatar")
    void uploadAvatar(
        @PathVariable Long id,
        @RequestPart("file") Resource file
    );
}

// 클라이언트 빈 등록 — HttpServiceProxyFactory로 프록시 생성
@Bean
public UserClient userClient(RestClient.Builder builder) {
    RestClient restClient = builder.baseUrl("https://api.example.com").build();
    return HttpServiceProxyFactory
        .builderFor(RestClientAdapter.create(restClient))
        .build()
        .createClient(UserClient.class);
}

인터페이스만 정의하면 실제 HTTP 호출 코드를 스프링이 알아서 만들어 줍니다. Feign Client와 비슷한데, 스프링 네이티브로 제공되니까 별도 의존성이 필요 없습니다.

6. Deprecated API 대량 제거

Boot 4.0에서 가장 주의해야 할 부분입니다. Boot 3.x에서 deprecated 경고가 뜨던 API들이 ** 전부 제거 **됩니다.

JAVA
// 제거되는 대표적인 API들

// 1. WebSecurityConfigurerAdapter — 이미 3.x에서 deprecated
// Before (제거됨!)
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/api/**").authenticated();
    }
}

// After — SecurityFilterChain 빈 방식
@Configuration
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(auth -> auth
            .requestMatchers("/api/**").authenticated()
        );
        return http.build();
    }
}

// 2. spring.factories 기반 자동 설정 — 완전 제거
// Before (META-INF/spring.factories) — 제거됨!
// org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
//   com.example.MyAutoConfiguration

// After (META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports)
// com.example.MyAutoConfiguration

7. Auto-Configuration 변경

@AutoConfiguration 어노테이션과 새로운 등록 방식이 ** 유일한 표준 **이 됩니다.

JAVA
// Boot 4.0의 Auto-Configuration 표준 방식

@AutoConfiguration(
    after = DataSourceAutoConfiguration.class,  // 순서 지정
    before = JpaRepositoriesAutoConfiguration.class
)
@ConditionalOnClass(DataSource.class)
@ConditionalOnProperty(
    prefix = "app.feature",
    name = "enabled",
    havingValue = "true"
)
public class MyFeatureAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public MyFeatureService myFeatureService() {
        return new MyFeatureService();
    }
}

등록 파일 위치도 확인하세요:

TEXT
# 파일 위치 (Boot 4.0에서 유일한 방식)
src/main/resources/META-INF/spring/
  org.springframework.boot.autoconfigure.AutoConfiguration.imports

# 파일 내용 — 한 줄에 하나씩
com.example.MyFeatureAutoConfiguration
com.example.AnotherAutoConfiguration

spring.factoriesEnableAutoConfiguration 키로 등록하던 방식은 완전히 제거됩니다. 3.x에서 병행 지원하던 게 4.0에서 끊기는 거예요.

8. Virtual Threads — 일급 시민으로 승격

Boot 3.2에서 프리뷰로 제공되던 Virtual Threads 지원이 4.0에서 ** 기본 옵션 수준 **으로 격상됩니다.

YAML
# application.yml — Virtual Threads 활성화
spring:
  threads:
    virtual:
      enabled: true  # 이 한 줄이면 끝

이 설정 하나로 다음이 전부 Virtual Threads를 사용합니다:

  • Tomcat 요청 처리 스레드
  • @Async 태스크 실행
  • @Scheduled 스케줄링
  • Spring MVC 비동기 처리

Virtual Threads의 핵심은 ** 코드를 바꾸지 않고 동시성을 개선 **할 수 있다는 점입니다. 기존 Tomcat 플랫폼 스레드(기본 200개)에서 사실상 무제한 Virtual Threads로 바뀌니까, 동시 요청 처리량이 크게 올라갑니다.

다만 주의할 점이 있습니다:

JAVA
// 주의 1: synchronized 대신 ReentrantLock 사용
// Before — Virtual Thread가 pin될 수 있음
public synchronized void updateCounter() {
    this.counter++;
}

// After — pinning 방지
private final ReentrantLock lock = new ReentrantLock();
public void updateCounter() {
    lock.lock();
    try { this.counter++; }
    finally { lock.unlock(); }
}

// 주의 2: ThreadLocal 대신 ScopedValue 고려
// Before — Virtual Thread마다 복사되어 메모리 낭비
private static final ThreadLocal<UserContext> context = new ThreadLocal<>();
// After — Java 21+ ScopedValue
private static final ScopedValue<UserContext> CONTEXT = ScopedValue.newInstance();

synchronized 블록이나 ThreadLocal 남용이 있으면 오히려 성능이 떨어질 수 있으니, Virtual Threads를 켜기 전에 반드시 점검하세요.

마이그레이션 체크리스트

실제로 마이그레이션할 때는 이 순서로 진행하면 안전합니다.

Step 1: 사전 준비 (Boot 3.x 상태에서)

BASH
# deprecated 경고 전부 해결 — 이게 가장 중요합니다
./gradlew compileJava 2>&1 | grep -i "deprecated"
# 또는 Maven
mvn compile -Dmaven.compiler.showDeprecation=true
  • Java 21 이상으로 업그레이드
  • Boot 3.x 최신 버전으로 먼저 업데이트
  • deprecated 경고 전부 해결
  • spring.factoriesAutoConfiguration.imports로 전환

Step 2: 의존성 업그레이드

GROOVY
// build.gradle
plugins { id 'org.springframework.boot' version '4.0.0' }
java { toolchain { languageVersion = JavaLanguageVersion.of(21) } }
  • Boot 4.0으로 버전 변경
  • 서드파티 라이브러리 호환성 확인 (QueryDSL, MapStruct, Lombok 등)
  • 빌드 실행 → 컴파일 에러 확인

Step 3: Jackson 3.0 + Hibernate 7.1 대응

  • Jackson import문 변경 (com.fasterxml.jackson.databindtools.jackson.databind)
  • 커스텀 Serializer/Deserializer 코드 수정
  • Hibernate 전용 Criteria API → JPA 표준으로 교체
  • @Type@JdbcTypeCode로 변경

Step 4: 테스트 및 검증

  • 단위 테스트 전체 통과
  • 통합 테스트 전체 통과
  • 스테이징 환경에서 E2E 테스트

자주 하는 실수

공부하다 보니 마이그레이션에서 자주 놓치는 부분이 몇 가지 있었습니다.

  1. Jackson 어노테이션 패키지까지 바꿔버리는 실수tools.jackson.annotation.JsonProperty는 존재하지 않습니다. 어노테이션은 com.fasterxml.jackson.annotation 그대로입니다.
  2. Virtual Threads 켜놓고 synchronized 방치 — Thread Pinning으로 성능이 오히려 떨어집니다. ReentrantLock으로 교체하세요.
  3. ** 서드파티 호환성 무시** — QueryDSL, MapStruct, Lombok 같은 코드 생성 도구는 반드시 호환 버전을 확인하세요.

정리

Boot 4.0 마이그레이션의 핵심을 한 줄로 요약하면:

deprecated 경고를 먼저 해결하고, Jackson 패키지명과 Java 21을 챙기면 절반은 끝입니다.

3.x → 4.0은 2.x → 3.x처럼 패키지 이름이 통째로 바뀌는 수준은 아닙니다. 하지만 Jackson 3.0의 패키지 변경, Hibernate 7.1의 API 제거, deprecated API 정리 등이 겹치면서 손이 가는 작업이 꽤 됩니다.

미리 Boot 3.x 최신 버전에서 deprecated 경고를 전부 해결해두면, 실제 마이그레이션할 때 훨씬 수월합니다. 한 번에 하려고 하지 말고 3.x 정리 → 4.0 업그레이드 순서로 단계별로 진행하는 게 가장 안전합니다.

댓글 로딩 중...