Spring Boot 4.0 마이그레이션은 코드 변경보다 서드파티 라이브러리 호환성이 진짜 병목이며, 빌드를 올리기 전에 의존성 전수 조사부터 해야 한다.
Spring Boot 4.0.5까지 나온 마당에, 여전히 3.x에서 업그레이드를 미루고 있는 팀이 꽤 많다. 2에서 3으로 넘어갈 때 javax에서 jakarta로 패키지를 전부 바꾸느라 며칠을 통째로 날린 기억이 생생할 테니 이해는 간다. 하지만 3.x 지원 종료가 다가오고 있고, 이번에도 무한정 미룰 수는 없다.
핵심 변화: Spring Framework 7 + Jakarta EE 11
Spring Boot 4.0의 가장 큰 변화는 기반 프레임워크가 Spring Framework 7로 교체된 것이다. Jakarta EE 11이 적용됐고, 최소 Java 버전도 올라갔다.
3.x에서 4.0으로 넘어갈 때 가장 많이 부딪히는 건 의외로 내 코드가 아니라 서드파티 의존성이다. IDE가 잡아주는 컴파일 에러는 고치면 그만이지만, 서드파티 라이브러리가 Spring Framework 7을 지원하지 않으면 내가 할 수 있는 건 기다리는 것뿐이다.
마이그레이션을 시도하면 보통 이런 흐름으로 전개된다:
버전 번호 올리고 빌드 — 일단 된다
테스트 실행 —
NoSuchMethodError가 등장한다스택 트레이스 추적 — 서드파티가 Spring Framework 6 내부 API를 직접 호출하고 있었다
라이브러리 최신 릴리즈 확인 — SF7 미지원
GitHub Issues에서 "Spring Boot 4 support" 검색 — "planned for next release"
대기
이 흐름은 2→3 때와 판박이다. 그때 배운 교훈이 있다면, 코드를 한 줄이라도 고치기 전에 의존성 호환부터 전수 조사하라는 것이다. 특히 사내 공통 라이브러리가 있는 조직에서는 이게 가장 큰 병목이 된다. 공통 라이브러리 팀이 SF7 대응을 완료할 때까지 나머지 서비스 팀 전체가 블로킹되는 구조가 흔하다.
현실적인 대응책이 하나 있긴 하다. 문제되는 라이브러리만 fork해서 SF7 호환 패치를 임시로 적용하는 것이다. 유지보수 부담이 생기지만 전체 마이그레이션이 한 라이브러리 때문에 멈추는 것보단 낫다. 다만 원본 라이브러리가 SF7을 공식 지원하는 즉시 fork를 걷어내겠다는 계획은 반드시 세워야 한다. 임시 fork가 영구 fork로 굳어지는 순간 관리 비용이 기하급수적으로 늘어난다.
올리기 전 체크리스트
| 항목 | 확인 방법 | 위험도 |
|---|---|---|
| Java 버전 | java -version — 최소 요구사항 충족 여부 |
높음 |
| 빌드 도구 | Gradle Plugin / Maven Parent 4.x 호환 | 중간 |
| 서드파티 라이브러리 | GitHub Issues에서 SF7 지원 여부 검색 | 매우 높음 |
| CI/CD 파이프라인 | Docker 이미지 JDK 버전, 빌드 스크립트 | 중간 |
QueryDSL, MapStruct, Flyway, Liquibase 같은 핵심 도구는 제일 먼저 확인한다. 공식 호환 매트릭스가 깔끔하게 정리돼 있으면 좋겠지만, 현실적으로는 각 프로젝트의 GitHub에서 이슈를 직접 뒤지는 게 가장 빠르다.
마이그레이션 실전 순서 — 빅뱅은 금물
프로덕션 서비스를 한 방에 올리겠다는 유혹은 이겨내야 한다. 리팩토링이나 기능 추가를 섞는 순간 에러의 원인이 마이그레이션인지 내 코드인지 구분이 안 된다.
1단계: 버전만 바꾼다. 별도 브랜치에서 Spring Boot 버전 번호만 변경하고 빌드한다. 다른 코드 변경은 일절 없이. 이 상태에서 터지는 에러만이 순수한 마이그레이션 이슈다.
2단계: Deprecated API를 정리한다. 3.x에서 deprecated 경고를 무시해왔다면 여기서 대가를 치른다. 제거된 API를 대체 메서드로 교체하는 게 작업량의 대부분이다. 이래서 CI에서 deprecation 경고를 에러로 잡아야 한다는 말이 반복되는 건데, 실제로 설정해둔 팀을 본 적은 별로 없다. Spring Security 쪽도 주의가 필요하다. 보안 관련 auto-configuration은 기본값이 조용히 바뀌는 경우가 있어서, 컴파일 에러 없이 런타임 동작만 달라질 수 있다.
3단계: 테스트를 전부 돌린다. 컴파일이 되더라도 안심하면 안 된다. Auto-configuration 변경이나 리플렉션 기반 설정은 런타임에서만 문제가 드러난다. 통합 테스트 커버리지가 높은 서비스는 여기서 그동안의 투자 효과를 체감하고, 낮은 서비스는 수동 QA 지옥을 맛본다. 이 차이가 마이그레이션 기간을 2배 이상 벌리는 경우도 흔하다.
4단계: 스테이징에서 최소 일주일은 돌린다. 프로덕션 트래픽 미러링이 가능하면 반드시 한다. JVM 동작이 달라지면서 메모리 사용 패턴이나 GC 행동이 바뀔 수 있다. 힙 사용량, GC 로그, 응답 시간 추이를 평소보다 촘촘하게 모니터링한다.
그래서 지금 올려야 하나
급하지 않으면 서두를 필요는 없다. Spring Boot 3.4의 지원 기간이 아직 남아 있으니, 3.3 이하를 쓰고 있다면 3.4로 먼저 올리는 게 올바른 순서다.
다만 빨리 움직여야 하는 경우도 있다. 보안 패치를 자체 백포트할 여력이 없는 소규모 팀이라면 최신 지원 버전에 머무르는 게 유일한 안전장치다. Virtual Thread 최적화나 GraalVM 네이티브 이미지처럼 4.0에서만 제대로 돌아가는 기능이 필요한 경우에도 미루는 비용이 올리는 비용을 넘어선다. 채용 공고에 "Spring Boot 4.0"을 적고 싶은 스타트업이라면 — 뭐, 그것도 나름의 합리적인 이유다.
준비 작업으로 하나 추천하자면, 마이그레이션 가이드를 정독하는 것보다 빈 프로젝트를 하나 4.0으로 생성해서 기존 코드를 조금씩 옮겨보는 게 체감이 빠르다. 문서에서는 "변경됨"이라고 한 줄로 끝나는 항목이, 실제로 옮겨보면 반나절짜리 삽질인 경우가 있다.
메이저 버전 마이그레이션은 기술 부채의 이자를 한꺼번에 상환하는 과정이다. 미루면 이자가 복리로 쌓이고, 한 번에 갚으면 고통스럽지만 깔끔하게 끝난다. 2→3 때보다 한 가지 나은 점이 있다면, 최소한 패키지 네임스페이스 대학살은 이번엔 없다는 거다.