Spring Boot 4 업그레이드, 지금 해야 할까?

“Boot 4 떴다는데, 우리도 올려야 하나요?”

새 버전이 나오면 늘 같은 갈등이 시작된다. 빨리 올리자니 운영 중인 서비스가 신경 쓰이고, 미루자니 점점 더 미루게 된다. Spring Boot 4도 마찬가지다. 2025년 11월에 4.0이 메이저 릴리즈됐고, 2026년 4월 23일에는 4.0.6 유지보수 릴리즈가 나오면서 슬랙 어딘가에서 같은 질문이 다시 나오는 중이다.

“Boot 4 4.0.6까지 떴다는데, 우리도 슬슬 봐야 되지 않아요?”

“지금 Boot 3.5인데 굳이? 잘 돌고 있는데.”

“근데 Framework 6.2가 6월에 오픈소스 지원 끝난다는 얘기 봤어요.”

이 글은 그 갈등에 대한 정리다. Spring Boot 4가 무엇을 바꿨는지, 지금 시점에 올려야 하는 사람은 누구이고 미뤄도 되는 사람은 누구인지, 그리고 실제로 마이그레이션을 시작할 때 미리 봐야 할 체크리스트가 무엇인지를 한 번에 본다. 시리즈 ‘Legacy Escape’의 첫 글이다.

새 버전이 떴을 때의 질문은 “더 좋아졌나”가 아니라 “내 코드에 무슨 일이 일어나는가”다.

결론 먼저: 누가 지금 봐야 하나

‘한 줄 요약: 새 프로젝트는 Boot 4 후보, Boot 3.5 운영팀은 검증 브랜치 만들 시점, Boot 2.x는 단계적 점프.’

상황별로 먼저 정리한다. 본문은 이 결론을 풀어쓰는 흐름이다.

  • ‘새 프로젝트라면’ Boot 4를 기본 후보로 두는 게 자연스럽다. 조직 제약(사내 공통 라이브러리, 배포 플랫폼 등)이 없다면.
  • ‘Boot 3.5 운영 서비스라면’ 지금 바로 옮기지 않더라도 ‘마이그레이션 검증 브랜치’는 만들어둘 시점이다. 분기 안에 옮기는 걸 목표로 삼는 게 현실적이다.
  • ‘Boot 2.x에 멈춰 있다면’ Boot 4로 바로 점프하지 말고, Java 17 + Boot 3.5를 거치는 ‘단계적 전환’이 안전하다.
  • ‘사내 공통 라이브러리, Spring Cloud, 보안 설정, Jackson 커스터마이징이 많은 프로젝트’는 일정이 더 길어진다. 라이브러리 호환부터 본다.

잠깐 — EOL과 LTS가 뭐예요?

‘EOL’은 End of Life의 줄임말이다. 소프트웨어가 EOL에 도달하면 새 기능 추가는 물론, 무료 공개 보안 패치와 버그 수정도 더 이상 기대하기 어렵다.

중요한 점: ‘EOL 다음 날 서비스가 멈추는 게 아니다.’ 다만 새 취약점이 발견됐을 때 공식 패치를 받기 어려워지고, 나중에 한꺼번에 업그레이드하려면 비용이 더 커진다.

‘LTS’는 Long-Term Support, 즉 장기 지원 버전이다. Java를 예로 들면 17, 21, 25가 LTS다. 운영 서비스는 보통 LTS 위에서 돌리는 게 안전하다.


한 장 요약: Spring Boot 4가 바꾼 것들

‘한 줄 요약: 단순 버전 업이 아니라 Framework 7 기반의 새 세대다. Java, Jakarta, Jackson, Null이 한꺼번에 움직였다.’

변화무슨 뜻인가운영 영향
Java 17~26최소 17, 최신 26까지 호환. 운영은 Java 21 또는 25 LTS를 조직 표준에 맞춰 검토빌드 이미지·CI JDK 버전 정리
Jakarta EE 11Servlet/JPA/Validation 표준 버전 상승 (Servlet 6.1 baseline)WAS 호환 확인. Undertow는 지원 제거
Jackson 3JSON 라이브러리 메이저 전환. group ID·package 일부 변경API 응답 JSON 회귀 테스트 필요
JSpecify null-safetynull 가능성 표준화IDE/정적 분석 경고 증가 가능
모듈화 / starter 구조Boot 모듈이 더 잘게 나뉨직접 의존성 박은 라이브러리 starter 재점검
테스트 API 변경@MockBean/@SpyBean 제거, MockMvc 자동 설정 변화테스트 코드부터 깨질 수 있음
Framework 6.2 EOL6세대 마지막 feature 브랜치, 2026-06 OSS 지원 종료Boot 3.5 운영팀 시계가 줄어드는 중

표 한 줄로 정리하면 이렇다. ‘한 자리 숫자 올라간 게 아니라, 백엔드 기준선 자체가 한 칸 옆으로 이동했다.’


왜 지금 봐야 하나

‘한 줄 요약: 새 버전이 떠서가 아니라, 기존 라인의 시계가 줄고 있어서다.’

업그레이드 글의 진짜 무게는 새 기능이 아니라 ‘기존 버전이 언제까지 안전한가’에 달려 있다. 2026년 4월 기준 상황은 이렇다.

  • ‘Spring Framework 6.2.x’는 6세대의 마지막 feature 브랜치다. OSS 지원 시계가 2026년 6월 말로 향하고 있다. 그 이후 보안 패치는 상용 지원 영역을 확인해야 한다.
  • ‘Spring Boot 3 계열’에서도 마지막 라인인 3.5.x를 기준으로 지원 시계를 봐야 한다. Boot 3.5는 Framework 6.2를 포함하는 라인이므로, Framework 6.2의 OSS 지원 종료 시점과 함께 마이그레이션 계획을 세우는 게 현실적이다.
  • ‘Spring Boot 4.0’은 2025년 11월 메이저 릴리즈, 2026년 4월에 4.0.6까지 유지보수 릴리즈가 쌓였다. 메이저가 막 나온 직후는 아니지만, 운영 반영 전에는 여전히 프로젝트별 검증이 필요하다.

즉 “새로운 게 나왔으니 옮긴다”가 아니라 “지금 쓰는 라인의 OSS 지원 시계가 줄고 있다”라는 게 동기다. 신규 프로젝트는 Boot 4로 시작하는 게 자연스럽고, 운영 중인 서비스도 늦지 않게 마이그레이션 계획을 세워둘 시점이다.


바뀐 기준선 자세히

‘한 줄 요약: Java, Jakarta, Jackson, Null — 네 가지가 가장 큰 변화다.’

1. Java: 17 이상, 25 LTS 권장 검토, 26까지 호환

Boot 4는 ‘Java 17을 최소 버전으로 유지’한다. Boot 3에서 Java 17로 이미 올린 팀은 한 번에 두 가지 점프(Java 버전 + Spring 버전)를 하지 않아도 된다. 2026년 4월 기준 Spring Boot 4.0.6의 시스템 요구사항은 ‘Java 17 이상, Java 26까지 호환’이며, 운영 기준으로는 최신 LTS인 ‘Java 25’를 함께 검토할 만하다.

Java 25를 같이 올릴지는 별도 판단이다. Java 8/11에서 올라오는 팀이라면 record, sealed class, pattern matching, virtual threads처럼 ‘Java 17~25 사이에 자리 잡은 모던 Java 기능’도 함께 검토하게 된다. (이 시리즈의 다음 글 주제다.)

2. Jakarta EE 11과 Servlet 6.1

잠깐 — Jakarta EE는 뭔가요?

Spring Boot는 혼자 웹 서버, DB, Validation을 전부 직접 구현하지 않는다. Servlet, JPA, Bean Validation 같은 자바 표준 위에서 동작한다. 이 표준 묶음을 ‘Jakarta EE’라고 보면 된다.

Boot 4는 이 표준 묶음의 기준선이 ‘Jakarta EE 11’로 올라간다. 그래서 웹 서버, JPA 구현체, Validation 라이브러리 호환성을 같이 확인해야 한다.

Boot 3에서 이미 한 번 큰 변화를 겪었다. javax.*jakarta.* 패키지 이동이 그것이다. Boot 4에서는 그 위에 ‘Jakarta EE 11’로 한 단계 더 올라간다. Servlet 6.1, JPA 3.2, Bean Validation 3.1 같은 표준 API의 새 버전이 적용된다.

실무에서 가장 큰 영향은 ‘Servlet 6.1 baseline’이다. 마이그레이션 가이드는 ‘Boot 4가 Servlet 6.1을 baseline으로 요구하며, Undertow는 Servlet 6.1과 아직 호환되지 않아 Boot 4에서 지원이 제거됐다’고 명시한다. Tomcat·Jetty 사용 팀은 호환 라인 확인만 하면 되지만, ‘Undertow 사용 중인 팀은 대체 검토’가 필수다.

3. Jackson 3

잠깐 — Jackson은 왜 중요한가요?

Spring Boot에서 REST API를 만들면 자바 객체를 JSON으로 바꿔 응답한다. 이때 내부에서 많이 쓰이는 라이브러리가 ‘Jackson’이다.

예를 들어 User{name="kim"} 객체를 { "name": "kim" } JSON으로 바꾸거나, 반대로 요청 JSON을 DTO 객체로 바꾸는 역할을 한다. 그래서 Jackson 버전이 바뀌면 ‘API 응답 모양, 날짜 포맷, null 처리 방식’이 달라질 수 있다.

Jackson 3 전환은 단순한 패치 업그레이드가 아니다. 마이그레이션 가이드에 따르면 ‘Jackson 3에서 group ID와 package name 일부가 바뀐다’. 핵심 라이브러리의 패키지가 com.fasterxml.jackson에서 tools.jackson으로 이동했다(예외: jackson-annotations는 기존 group/package 유지). 또한 Boot 4는 Jackson 2 호환 모듈을 제공하지만 deprecated 형태이고 향후 제거될 예정이라고 안내한다.

기본 ObjectMapper만 쓰는 프로젝트는 영향이 작을 수 있지만, ‘custom serializer/deserializer를 짠 프로젝트, ObjectMapper를 직접 설정하는 프로젝트, JSON 응답 스냅샷 테스트가 많은 프로젝트는 반드시 회귀 테스트가 필요하다.’

4. JSpecify 기반 null-safety

이번 변화 중 ‘코드 톤이 가장 크게 바뀌는’ 부분이다. Boot 3까지는 @Nullable을 Spring 자체 어노테이션, IntelliJ 어노테이션, JSR-305 등 여러 출처에서 가져다 썼다. ‘Spring Boot 4 / Spring Framework 7 라인에서 JSpecify 기반 null-safety가 본격 반영’되며, Spring 포트폴리오 전반이 같은 흐름을 따라가고 있다.

JSpecify는 ‘Java의 null 표현을 표준화하기 위한 커뮤니티 표준’이다. 정적 분석 도구(IntelliJ, Error Prone, NullAway 등)가 같은 어노테이션을 같은 의미로 해석하도록 만든다. 결과적으로 IDE 경고와 빌드 분석 결과가 더 정확해지고, “이 메서드 결과가 null일 수 있는가”라는 질문이 코드 안에 명시적으로 적힌다.


마이그레이션 전 체크리스트

‘한 줄 요약: 코드만 보지 말고, 빌드 도구·의존성·테스트·CI/CD까지 함께 본다.’

본격 마이그레이션 전에 확인할 항목들이다. 운영 중인 서비스라면 별도 브랜치에서 한 항목씩 검증하는 편이 안전하다.

영역확인할 것
Java 버전17 이상인가? 25 LTS로 갈지 17에 머무를지 결정 (4.0.6은 26까지 호환)
Gradle / MavenMaven 3.6.3 이상, Gradle 8.14 이상 또는 9.x. CI 빌드 이미지도 함께
javax.* 잔재Boot 2 → 3 전환 때 남은 javax.servlet, javax.persistence import가 없나
Servlet ContainerTomcat/Jetty는 Boot 4 지원 라인 확인. Undertow 사용 중이면 대체 필요
Jackson 커스텀 설정직접 설정한 ObjectMapper, custom serializer/deserializer, group/package 변경 영향
Spring Security6.x → 7.x 변화. SecurityFilterChain 설정과 deprecated API 정리
JPA / HibernateHibernate 메이저 버전 변화, EntityManager 동작 차이
테스트@MockBean/@SpyBean 제거, @MockitoBean/@MockitoSpyBean로 대체, MockMvc 자동 설정 변화
Testcontainers / Mockito / AssertJ호환 버전 확인
Flyway / LiquibaseDB 마이그레이션 도구 호환. 직접 의존성보다 spring-boot-starter-* 검토
빌드 / CIGitHub Actions, GitLab CI 등 빌드 이미지의 JDK 버전
컨테이너Dockerfile 베이스 이미지, GraalVM Native 사용 시 GraalVM 25 라인
관측성Micrometer, OpenTelemetry 어댑터 호환

이 표가 길어 보이지만, 대부분 ‘Boot 3에서 이미 한 번 정리한 영역’이다. Boot 3 → 4 전환은 보통 Boot 2 → 3 만큼 거칠지 않다. 단 ‘Servlet 컨테이너(Undertow)와 테스트 API’는 Boot 3에서 정리되지 않은 신규 변화이므로 주의해서 본다.


실무 접근법

‘한 줄 요약: 한 번에 옮기지 말고, 별도 브랜치에서 매트릭스 테스트부터 돌린다.’

마이그레이션의 실제 흐름은 이렇게 잡는 편이 안전하다.

  • ‘별도 브랜치에서 시작.’ main에 직접 손대지 않는다. migration/spring-boot-4 같은 브랜치를 따로 두고 의존성 충돌부터 푼다.
  • ‘의존성 충돌부터 확인.’ gradle dependencyInsight 또는 mvn dependency:tree로 Boot 4가 끌고 오는 라이브러리와 우리 프로젝트가 직접 명시한 버전이 충돌하지 않는지 본다. 이 단계에서 가장 시간이 많이 든다.
  • ‘컴파일 통과부터 잡고, 테스트는 그다음.’ Boot 4에서는 운영 코드는 컴파일되는데 테스트 코드가 먼저 깨지는 경우가 꽤 나올 수 있다(@MockBean 같은 변화 때문). 테스트 코드 마이그레이션을 별도 작업 단위로 잡는다.
  • ‘API 회귀 테스트 먼저.’ 외부 API 응답 형식이 같은지 (특히 Jackson 3 영향). MockMvc로 짠 회귀 테스트가 있다면 여기서 큰 도움이 된다.
  • ‘운영 환경 수준 매트릭스 테스트.’ staging에 올려서 부하·메모리 사용량·로그 패턴이 기존과 같은지 본다. 일주일 정도는 관찰하는 편이 안전하다.
  • ‘관측성과 로그 먼저 확인하고 배포.’ 트레이스 ID 형식, 메트릭 이름, 로그 포맷이 바뀌지 않았는지 미리 확인한다. 배포 직후 대시보드에서 이상치가 보이면 빠르게 잡는다.

새 기능 추가는 마이그레이션이 끝난 다음에 해도 늦지 않다. 한 번에 두 가지를 하면 디버깅 비용이 곱으로 늘어난다.

업그레이드는 한 번의 점프가 아니라 다섯 단계의 흐름이다.

그래서 지금 올려야 하나 (상황별)

‘한 줄 요약: 신규는 Boot 4 후보, Boot 3.5는 분기 안에 계획 수립, Boot 2~3.4는 압박이 더 크다.’

상황권장
신규 프로젝트 시작특별한 조직 제약이 없다면 Spring Boot 4를 기본 후보. Java 25 LTS도 같이 검토
운영 중인 Spring Boot 3.5분기 안에 마이그레이션 계획 수립. 검증 브랜치는 지금 만들어둘 시점
Spring Boot 3.0~3.4시계가 더 짧다. 보안 패치 라인 확인 후 일정 더 빠르게
Spring Boot 2.x (Java 8/11)가장 무거운 케이스. Boot 2 → 3 → 4를 두 단계로 끊어서. Java 17부터 먼저
사내 라이브러리·플러그인 의존이 큰 환경Boot 4 호환 라이브러리부터 확인 후 일정 잡기

운영 서비스에서 가장 흔한 함정은 ‘괜찮으니까 미루다가 EOL 직전에 급하게 올리는’ 패턴이다. 그 시점엔 이미 라이브러리들도 Boot 4 기준으로 굴러가고 있어서, 의존성 충돌이 더 심해진다. 한두 분기 여유를 두고 옮기는 게 결과적으로 더 싸다.


마치며: Legacy Escape 시리즈 첫 글로

이 글은 시리즈 ‘Legacy Escape’의 1편이다. 운영 중인 프로젝트가 EOL 시계 앞에서 어떻게 결정하고, 어떤 순서로 옮기는지를 한 글씩 정리할 예정이다.

다음 글은 ‘Java 8/11 개발자가 Java 17/21/25로 오면서 만나는 모던 Java’다. record, sealed class, pattern matching, virtual threads처럼 Java 17~25 사이에 자리 잡은 기능을 실무 영향 위주로 정리한다. Boot 4 글에서 미뤄둔 ‘Java 25를 같이 올릴지’ 결정에 직접 도움이 될 글이다.

지금까지의 Spring Garden 시리즈가 ‘Spring을 어떻게 쓰는가’였다면, Legacy Escape는 ‘쓰던 Spring을 어떻게 옮기는가’에 가깝다. 두 시리즈가 같이 묶여 읽히면 더 좋다.


📦 개발자를 위한 추가 메모

일반 독자는 여기부터 건너뛰어도 된다. 아래는 실제 마이그레이션을 시작할 때 손에 들고 있으면 좋은 보충이다.

의존성 라인 한 번에 정리

도구권장 라인 (2026-04 기준)
Spring Boot4.0.6
Spring Framework7.0.x
Java17 LTS (호환), 25 LTS (운영 권장 검토), 26까지 호환
Maven3.6.3 이상 (실무 권장: 최신 패치)
Gradle8.14 이상 또는 9.x
Hibernate ORMBoot 4가 끌고 오는 라인 사용 (직접 명시 자제)
Jackson3.x (Boot 4가 자동 관리)
TestcontainersBoot 4 호환 최신 라인
MicrometerBoot 4가 끌고 오는 라인
GraalVM25.x (Native 빌드 시)

핵심: 가능하면 의존성 버전을 직접 박지 말고 Boot 의존성 관리(BOM)에 맡긴다. 직접 명시한 버전이 많을수록 충돌이 잦다.

Boot 4에서 starter 구조도 바뀐다

Boot 4는 내부 모듈 구조가 더 잘게 나뉘었다. 대부분의 프로젝트는 기존 starter를 쓰면 큰 문제가 없지만, ‘특정 라이브러리를 직접 의존성으로 넣어 쓰던 프로젝트’는 새 starter를 확인해야 한다.

예를 들어 Flyway나 Liquibase를 직접 의존성으로만 추가했다면, Boot 4에서는 spring-boot-starter-flyway, spring-boot-starter-liquibase 사용을 검토한다. starter가 BOM의 의존성 관리와 자동 설정을 함께 가져오기 때문에, 라이브러리 버전 충돌과 자동 설정 누락을 동시에 줄여준다.

Jackson 3 전환 포인트

build.gradle.kts 예시:

dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
// Jackson 3는 Boot 4가 관리한다.
// Jackson 2가 꼭 필요한 라이브러리가 있다면 spring-boot-jackson2를 임시 호환책으로 검토하되,
// 장기적으로는 Jackson 3 전환을 목표로 한다.
}

직접 짠 ObjectMapper 설정이 있다면 다음을 점검한다.

  • JavaTimeModule 등록 방식이 동일한가
  • @JsonFormat, @JsonInclude 동작이 같은가
  • 빈 객체({})·null 처리 기본값이 기대대로인가
  • import 경로에 com.fasterxml.jackson 잔재가 없는가 (tools.jackson으로 이동)

API 회귀 테스트로 응답 JSON 스냅샷을 비교하면 차이를 빨리 발견할 수 있다.

테스트 API 변경: @MockBean → @MockitoBean

Boot 4에서 가장 빨리 깨지는 곳은 테스트 코드다. 운영 코드는 컴파일되는데 테스트가 무더기로 빨갛게 변하는 경우가 잦다.

// Boot 3.x
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;

@SpringBootTest
class UserServiceTest {
    @MockBean UserRepository repository;
    @SpyBean EmailSender sender;
}

// Boot 4
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;

@SpringBootTest
@AutoConfigureMockMvc  // MockMvc는 더 이상 자동 구성되지 않음
class UserServiceTest {
    @MockitoBean UserRepository repository;
    @MockitoSpyBean EmailSender sender;
}

이 한 줄 차이가 테스트 모듈 전체에 퍼져 있을 수 있다. IDE의 일괄 변경 기능이나 Spring Boot ‘properties migrator’ 같은 도구가 도움이 된다.

JSpecify 도입 패턴

JSpecify 어노테이션은 단계적으로 도입한다. 처음에는 ‘public API 경계’에만 붙이고, 점차 내부로 확장한다. 패키지 단위 기본값과 메서드 단위 예외 표시는 분리해서 보는 편이 헷갈리지 않는다.

// package-info.java
@NullMarked
package com.example.user;

import org.jspecify.annotations.NullMarked;
// UserService.java
import org.jspecify.annotations.Nullable;

public class UserService {

    public User findById(Long id) {
        // 패키지 기본값(@NullMarked)에 따라 not-null로 해석
    }

    public @Nullable User findByEmail(String email) {
        // null 가능성을 명시
    }
}

@NullMarked는 module/package/class/method에 붙일 수 있고, 그 범위 안의 unannotated 타입을 기본 not-null로 취급한다. 단, 패키지에 붙인다고 ‘하위 패키지’까지 자동 적용되는 건 아니다. 하위 패키지마다 별도 package-info.java가 필요하다.

Spring Security 설정 점검

Boot 3 시점에 이미 WebSecurityConfigurerAdapter 폐지를 따라간 팀은 큰 부담이 없다. Boot 4에서는 그 위에 추가로 deprecated API들이 정리됐을 가능성이 있으므로 빌드 경고를 모두 잡는 단계가 필요하다. CSRF, 세션, OAuth2 설정의 기본값 변화도 함께 확인한다. (Spring Garden 1·2편 참고.)

추가로, Spring Security 7 라인에는 ‘MFA(다단계 인증)·Authorization Server 통합·modular configuration’ 같은 변화가 들어와 있다. 사내 인증 서버, OAuth2 Provider, MFA 정책을 직접 운영하는 팀이라면 빌드 경고만 잡는 수준에서 끝나지 않는다. ‘Security 비중이 큰 팀은 Spring Security 7 마이그레이션 가이드를 별도로 확인’하는 단계를 일정에 넣는 편이 안전하다.

매트릭스 테스트 예시

GitHub Actions로 Boot 3.5와 Boot 4를 병렬 빌드해 결과를 비교하는 방식이 안전하다.

strategy:
  matrix:
    boot-version: [ "3.5.x", "4.0.6" ]
    java-version: [ "17", "25" ]

이렇게 두면 ‘Boot 4 + Java 17’, ‘Boot 4 + Java 25’, ‘Boot 3.5 + Java 17’ 조합이 동시에 빌드된다. 회귀 테스트가 어디서 실패하는지 한 번에 보인다.

마이그레이션을 한 줄로

”Boot 4가 좋아서가 아니라, Boot 3·Framework 6의 시계가 줄고 있어서 옮긴다.” 이 관점이 가장 정직하다. 마이그레이션은 새 기능 도입이 아니라 ‘기준선을 다음 칸으로 옮기는 일상적인 운영 작업’이다.

댓글 남기기