Spring Boot 4.1까지 나온 지금, 무엇이 바뀌었고 무엇을 먼저 준비해야 할까요

Spring Boot 4로 올린 서비스 하나가 기동조차 하지 못한 적이 있습니다. 로그는 짧았습니다. 보안 설정 클래스가 Jackson2ObjectMapperBuilder 빈을 주입받으려 했는데, 그런 빈이 없다는 에러였습니다. Boot 3에서는 늘 있던 빈입니다. Boot 4는 Jackson 3를 기본으로 삼으면서 그 빈을 더 이상 만들지 않습니다. 버전을 올리자 애플리케이션 컨텍스트가 아예 뜨지 않았습니다.
이런 일은 앞으로 더 자주 보게 될 겁니다. 2026년 6월 30일로 Spring Boot 3.5의 오픈소스 지원이 끝났기 때문입니다. 3.x에 남아 있는 서비스는 이제 무료 보안 패치를 받지 못합니다. 옮길지 말지가 아니라 언제 어떻게 옮길지를 정해야 하는 시점이 됐습니다. 이 글에서는 Boot 4가 지금 어디까지 왔는지, 무엇이 크게 바뀌었는지, 저희가 옮기면서 릴리스 노트만으로는 보이지 않았던 것이 무엇인지, 그리고 Spring 생태계가 어디로 가고 있는지를 차례로 짚어 보겠습니다.
지금 어디쯤인가
Boot 4는 이미 두 번째 마이너 버전까지 나와 있습니다.

두 가지를 눈여겨볼 만합니다. 첫째, Boot 4.0의 오픈소스 지원도 올해 말이면 끝납니다. 지금 옮기는 팀이라면 4.0이 아니라 4.1을 목표로 잡는 게 자연스럽습니다. 둘째, Boot 3.5만 상용 지원이 2032년까지 길게 잡혀 있습니다. 당장 옮기기 어려운 조직을 위한 출구이지만, 무료로 버틸 수 있는 기간은 이미 지났습니다.

Boot 4.0에서 크게 바뀐 것
릴리스 노트는 길지만, 실제로 코드를 건드리게 만드는 변화는 몇 갈래로 모입니다.
자동 설정이 잘게 쪼개졌습니다.
하나로 뭉쳐 있던 자동 설정이 기술별 모듈로 나뉘었고, 스타터 이름도 따라 바뀌었습니다. spring-boot-starter-web은 spring-boot-starter-webmvc를 권장하고, OAuth2 스타터는 security-oauth2-*로 옮겨 갔습니다. 테스트 슬라이스도 마찬가지입니다. @WebMvcTest나 @DataJdbcTest가 기술별 테스트 모듈로 이사해서, import 경로가 바뀌고 해당 테스트 모듈을 의존성에 따로 추가해야 합니다. 한 번에 옮기기 부담스럽다면, 모든 모듈을 담은 spring-boot-starter-classic 계열 스타터로 먼저 올린 뒤 필요한 모듈만 남기는 2단계 이행도 공식적으로 안내돼 있습니다.
Jackson 3가 기본입니다.
패키지가 com.fasterxml.jackson에서 tools.jackson으로 바뀌었고(어노테이션은 그대로입니다), Boot의 커스터마이저도 JsonMapperBuilderCustomizer로 이름이 바뀌었습니다. Jackson 2는 spring-boot-jackson2 모듈로 당분간 쓸 수 있지만 deprecated입니다. 처음에 적은 기동 실패가 이 변화에서 나왔습니다.
null 안전성이 JSpecify로 통일됐습니다.
Spring 전체가 JSR 305 기반 어노테이션을 걷어내고 JSpecify로 옮겼습니다. 제네릭과 배열, 가변 인자까지 nullness를 표현합니다. Kotlin에서는 Spring API의 nullable 여부가 더 정확하게 보이는 대신, 기존 코드에서 컴파일 오류가 새로 날 수 있습니다.
웹 계층에 표준 도구가 늘었습니다.
Spring MVC와 WebFlux에 API 버저닝이 정식으로 들어왔고, @HttpExchange로 선언한 HTTP 인터페이스 클라이언트를 자동 설정으로 바로 쓸 수 있습니다. 재시도와 동시성 제한(@Retryable, @ConcurrencyLimit)은 Spring Framework 자체 기능이 됐고, Boot는 더 이상 Spring Retry의 버전을 관리하지 않습니다.
빠진 것도 있습니다.
Undertow는 Servlet 6.1과 맞지 않아 제외됐고, 테스트의 @MockBean과 @SpyBean은 @MockitoBean과 @MockitoSpyBean으로 바뀌었습니다. 실행 가능한 JAR에 붙던 임베디드 실행 스크립트도 사라졌습니다.
4.1과 4.2: 4.0에 오래 머물기 어려운 이유
Boot 4.1은 gRPC 서버와 클라이언트 지원을 넣고, HTTP 클라이언트에 SSRF 방어용 InetAddressFilter를 붙이고, @Async 메서드로 컨텍스트가 전파되게 했습니다. 4.0에서 빠졌던 Spock 지원은 Groovy 5를 지원하는 Spock 2.4가 나오면서 돌아왔습니다.
더 중요한 건 업그레이드 절에 적힌 한 줄입니다. 4.0에서 deprecated였던 클래스와 메서드, 프로퍼티가 4.1에서 제거됐습니다. 4.0으로 옮기면서 deprecated 경고를 남겨 둔 채 넘어갔다면, 4.1로 가는 길에 그 빚을 갚아야 합니다. 4.2는 현재 두 번째 마일스톤으로, SSL 번들 기반 LDAPS와 OpenTelemetry 시맨틱 컨벤션 지원이 들어갔고 11월에 Spring Framework 7.1과 함께 나올 예정입니다.
옮기며 알게 된 것: 응답 JSON이 조용히 바뀐다
저희는 사내 공용 라이브러리부터 Boot 4로 옮기기 시작했습니다. 컴파일 오류와 기동 실패는 오히려 쉬웠습니다. 요란하게 터지니 찾을 수 있습니다. 어려운 건 아무 오류 없이 바뀌는 것이었고, 그 대부분이 Jackson 3의 기본값에서 나왔습니다.
Jackson 3는 여러 기본값을 뒤집었습니다. 마이그레이션 가이드에도 적혀 있는 것부터 보면, 프로퍼티를 알파벳 순으로 정렬하고, enum을 name()이 아니라 toString()으로 읽고 쓰고, 날짜를 타임스탬프 배열이 아닌 ISO 문자열로 씁니다. 같은 객체를 두 버전의 기본 설정으로 직렬화해 보면 차이가 한눈에 보입니다.
Jackson 2 {"zeta":"z","alpha":"a","duration":90.000000000,"year":2022,"date":[2026,9,29],"color":"RED"}
Jackson 3 {"alpha":"a","color":"red-display","date":"2026-09-29","duration":"PT1M30S","year":2022,"zeta":"z"}필드 순서가 바뀌고, enum이 toString() 값으로 나가고, 날짜가 문자열이 됐습니다. 그런데 이 결과에는 가이드의 기본값 목록에 없는 차이도 하나 있습니다. Duration입니다. 같은 90초가 Jackson 2에서는 90.000000000, Jackson 3에서는 "PT1M30S"로 나갑니다. java.time 지원이 별도 모듈에서 databind 본체로 들어오면서 생긴 차이로 보이는데, 가이드의 기본값 변경 목록에는 빠져 있습니다.
Year는 더 까다로웠습니다. 기본 설정에서는 두 버전 모두 2022라는 숫자를 씁니다. 그런데 날짜를 타임스탬프로 쓰지 않도록 설정하면 달라집니다. 이 설정은 날짜를 사람이 읽을 수 있는 문자열로 내보내려고 많은 팀이 켜 두는 것이고, 저희도 그렇습니다.
날짜 타임스탬프 끔, Jackson 2 {"year":"2022", ...}
날짜 타임스탬프 끔, Jackson 3 {"year":2022, ...}Jackson 2는 이 설정을 Year에도 적용해 문자열로 쓰지만, Jackson 3는 숫자로 씁니다. 응답을 받는 쪽이 문자열을 기대하고 있다면, 서버 버전을 올리는 순간 클라이언트가 깨집니다. 기본 설정으로 테스트하면 드러나지 않는 차이라 더 위험합니다.

저희가 이 차이들을 찾은 방법은 단순합니다. 공용 라이브러리에 Jackson 2용과 Jackson 3용 자동 설정을 따로 두고, 같은 객체를 두 설정으로 직렬화해 결과를 비교하는 테스트를 만들었습니다. 이 테스트가 Duration과 Year의 차이를 잡았고, 저희는 Jackson 3 쪽 설정에 정렬과 enum 동작을 Jackson 2처럼 되돌리는 옵션을 켜고, Year는 전용 직렬화 모듈로 문자열을 유지했습니다. Jackson 3에는 JsonMapper.builderWithJackson2Defaults()라는 도우미도 있지만, 기본 설정 기준의 호환이라 저희처럼 날짜 설정을 바꿔 둔 경우의 Year 차이까지 막아 주지는 않습니다.
같은 작업에서 조용한 실패가 두 개 더 나왔습니다. 하나는 Jackson 3 설정 코드에서 모듈 목록을 비우고 다시 등록하던 한 줄이었습니다. Boot의 표준 커스터마이저가 먼저 등록해 둔 Kotlin 모듈과 사내 커스텀 모듈까지 지워 버리는 코드였는데, 아무 오류도 내지 않았습니다. 다른 하나는 사내 식별자 타입의 직렬화 모듈이었습니다. Jackson 3용 구현은 만들어 두고 빈으로 등록하지 않아서, Jackson 3로 옮긴 서비스에서만 그 타입의 직렬화가 아무 오류 없이 동작하지 않았습니다.
돌아보면 규칙은 하나였습니다. 기동 실패와 컴파일 오류는 알아서 드러나지만, 응답 JSON의 모양은 누군가 비교해 보기 전까지 드러나지 않습니다. 버전을 올리기 전에 주요 응답 몇 개를 두 버전으로 직렬화해 비교하는 테스트를 먼저 두는 게, 저희가 찾은 가장 싼 안전장치 였습니다.
Spring은 어디로 가고 있나
공식 발표와 로드맵에서 읽히는 방향은 크게 네 가지입니다.
HTTP 클라이언트 정리. Spring Framework 7.1 릴리스 노트는 "RestTemplate과 관련 타입은 7.1부터 정식으로 deprecated"라고 못 박았습니다. 실제 제거는 아직 일정이 잡히지 않은 Framework 8.0으로 미뤄 두었습니다. 당장 사라지지는 않지만, 새 코드는 RestClient나 @HttpExchange 인터페이스로 쓰라는 신호입니다. 7.1에는 요청 본문을 가진 멱등 메서드인 HTTP QUERY 지원과 새 멀티파트 메시지 컨버터도 들어갑니다.
Jackson 3와 null 안전성은 생태계 전체의 기준이 됩니다. Framework 7.1은 Jackson 3.1을 기준 버전으로 올렸습니다. Spring AI 2.0도 Jackson 3로 옮겼고 코드 전체에 JSpecify 어노테이션을 달았습니다. Boot만 Jackson 2에 묶어 두고 버티는 선택지는 점점 좁아집니다.
AI 쪽은 Boot 4를 전제로 움직입니다. 2026년 6월 12일에 나온 Spring AI 2.0은 Boot 4.0과 4.1, Framework 7.0을 기준으로 설계됐다고 밝혔습니다. 도구 호출을 advisor 체인의 구성 요소로 끌어올렸고, MCP Java SDK 2.0을 넣으면서 기본 전송 방식을 SSE에서 Streamable HTTP로 바꿨습니다. Spring으로 AI 기능을 붙이려는 팀에게 Boot 4 이행은 선행 조건이 됐습니다.
관측성은 OpenTelemetry 중심으로 모입니다. Boot 4.0에 OpenTelemetry 스타터가 생겼고, 4.1은 SDK를 끄고 켜는 스위치와 샘플러, 로그 프로세서 설정을 더했고, 4.2 마일스톤은 OpenTelemetry 시맨틱 컨벤션을 지원합니다. 버전마다 한 걸음씩 OpenTelemetry 쪽으로 옮겨 가고 있습니다.
다음 메이저인 Framework 8.0과 Boot 5는 아직 일정이 공개되지 않았습니다. 지금 확실한 건 11월에 Boot 4.2와 Framework 7.1이 나온다는 것까지입니다.
다시, 기동하지 않던 서비스
처음의 기동 실패는 Jackson2ObjectMapperBuilder 주입을 걷어내고 매퍼를 직접 만들도록 고친 뒤, 애플리케이션 컨텍스트가 뜨는지 확인하는 회귀 테스트를 붙여서 끝냈습니다. 그 실패는 오히려 고마운 쪽이었습니다. 요란하게 터졌으니까요. 저희가 이번 이행에서 가장 오래 붙들고 있던 건 아무 로그도 남기지 않은 Duration과 Year였습니다.
Boot 4로 가는 길을 지금 준비한다면, 저희가 권하고 싶은 순서는 이렇습니다.
- 목표는 4.0이 아니라 4.1로 잡습니다. 4.0의 무료 지원도 연말에 끝납니다
- 3.5에서 먼저 deprecated 경고를 정리합니다. 4.0에서 지운 것과 4.1에서 지운 것이 따로 있습니다
- 버전을 올리기 전에 주요 응답을 Jackson 2와 3으로 직렬화해 비교하는 테스트를 둡니다
- 기동 확인용 컨텍스트 로딩 테스트를 서비스마다 하나씩 둡니다
릴리스 날짜와 지원 기간은 spring.io의 프로젝트 정보(2026-09-27 조회)를, 기능 설명은 각 버전의 공식 릴리스 노트와 블로그를 근거로 했습니다. Jackson 직렬화 비교는 Jackson 2.22.1(JavaTimeModule 등록)과 Jackson 3.0.4의 기본 설정으로 저자 팀이 직접 실행한 결과입니다.
🚀플렉스팀 채용페이지 바로가기
☕flex Private Talk 신청하기