
사이드 프로젝트를 시작하려고 설정 중인데 갑자기 낯선 숫자가 보였다.
가볍게 4.0.0 버전의 변경점을 확인하려고 서칭해보았는데 단순히 바뀐 버전 업이 아닌 일종의 게임 체인저(?) 로 평가하는 사람이 있는 만큼 제대로 알아봐야 겠다고 느꼈다.
해당 포스팅은 Spring Framework 7.0 과 Spring Boot 4.0 그리고 JDK 25에 대해 알아본다.
해당 블로그의 Retryable 의존성을 추가하지 않고 내부적으로 라이브러리 지원, API 버저닝 등등... 성능 뿐만 아니라 코드에 직관적으로 영향을 끼칠 수 있는 것들이 다수 보였기 때문에 더욱 관심이 생겼다...
Spring Framework 7.0
사실 SpringBoot 4.0은 같이 새롭게 출시된 Spring Framework 7.0을 기반으로 두기 때문에 Spring Framework 7.0 Release Notes를 봐야한다.
Breaking Changs
HttpHeaders API MultiValueMap 상속 제거
SpringBoot 3.x.x

SpringBoot 4.0.0

기존에는 Map 자료형을 상속 받기 때문에 HttpHeader를 다루는 본래 목적에는 불필요한 메서드가 제공되고 있었다.
또한 HttpHeader에서 대소문자를 구분하지 않기에 Cookie COOKIE cookie 모두 같은 값으로 인식되어야 하지만 Key값이 다른 이유로 중복돼서 저장이 되는 문제 또한 4.0.0 에서는 LinkedCaseInsensitiveMap을 사용하여 대소문자를 구분하지 않는 Map 자료형을 사용한다.
SpringBoot 3.x.x 에서는 compute() 같은 Map API를 사용할 수 있지만

SpringBoot 4.0.0 에서는 상속을 제거하고 내부로 감췄기 때문에 사용할 수 없고, HttpHeader API에서 불필요한 기능을 제공하지 않게 됐다.

SpringExtension 컨텍스트 범위 변경
먼저 SpringExtension은 각 테스트가 동작하는 환경에 대한 설정 정보를 관리하는 역할을 한다.
이 내용이 Breaking Changes인 이유는 테스트 코드에서 @Nested를 사용할 때 특수한 경우에 문제가 발생할 경우가 있기 때문이다.
기존 Spring Framework 6.x.x 버전에는 이러한 컨텍스트가 최상위 클래스 기준으로 관리되었기 때문에 @Nested를 사용하여 내부 클래스를 정의하더라도 내부 클래스에서 재정의된 인스턴스 또한 문제 없이 할당되지만,
7.0.0 버전에서는 메서드 기준으로 변경되면서 @Nested 내부 클래스에 재정의된 인스턴스가 할당이 제대로 되지 않을 수 있다고 한다. (사실 대부분 @Nested를 복잡한 DI 관계 설정을 하는 경우에만 발생하는 문제고 단순 그룹핑을 하는 경우에는 발생하지 않는다.)
Deprecations
RestTemplate
7.0 참조 문서 부터 Deprecated로 표시되면서 7.1부터 공식으로 API에 마킹된다.
따라서 대체 기술이자 현재의 HTTP 클라이언트의 표준인 HttpClient/WebClient(동기 / 비동기)를 사용한다.
Jackson 2.x
대신 Jackson 3.x가 등장했다. Json 직렬화/역직렬화에 사용되던 라이브러리다.
Null Safety
Spring 내부적으로 사용되던 Null 체크 방식이 변경되었다. Java 개발자는 크게 변경되는 사항이 없으나 Kotlin 개발에선 기존 Null 체크 방식이 다소 변경되어 오류가 발생할 수 있다.
Servlet 6.1 and WebSocket 2.2
Spring Framework 7.0 에서 채택된 버전으로 사용해야 하는 웹 서버 버전 변경업과 테스트 도구 또한 개선되었다.
JPA 3.2 and Hibernate ORM 7.1/7.2
Spring Framework 6.x.x 에서는 @Autowired로 하면 컴파일 에러가 난다.

Spring Framework 7.x.x 에서는 에러가 나지 않는다.

주로 테스트 코드에서 EntityManager를 주입 받을 때, @PersistenceContext를 사용한다.
이전 버전에서도 @Autowired를 사용할 수 있는 방법이 몇가지 존재했지만 이번 7.0.0 부터 공식적으로 지원하게 되었다.
JMS destination handling
Java Message Service 로 기본 설정 값이 변경되었다. 이는 다뤄보지 못한 영역이라 자세한 설명은 어렵지만 디폴트 설정을 세션 단위 캐싱으로 해놓았고 원하지 않는다면 간단히 DynamicDestinationResolver로 설정을 변경하면 된다.
Google Gson support in WebFlux
Spring에서 내장되어있는 Json 직렬화/역직렬화 라이브러리는 Jackson이다.
비슷한 라이브러리가 Google Gson이다. 기존 Jackson 라이브러리를 사용할 때, 동기/비동기 처리 모두 문제없었지만 Gson에서 지원이 되지 않던 문제를 해결했다.
GraalVM Native Applications
이 변경점을 이해하기 위해 GraalVM을 알아야 한다.
그리고 이 변경점은 "닫힌 세계" 제약을 해결하기 위한 RuntimeHints 작성 방식 변경이다.
CORS Pre-Flight requests behavior change
기존에는 모든 요청에 대해 CORS 설정을 확인하고 비어있다면 거부 했지만, 이제는 거부하지 않는다.
Programmatic Bean Registration
기존에는 @Bean 생성 시, 하나의 @Bean 메서드에는 하나의 빈만 정의 가능하고, 명확한 타입을 반환해야 하는 제약조건을 우회하는 방법을 BeanRegistrar인터페이스 형태로 제공한다.
자세한 사용 법은 공식 문서를 참조한다.
Resilience features: RetryTemplate, @Retryable, @ConcurrencyLimit
@Retryable 애노테이션을 사용하려면 의존성을 추가해야 했다.
이제는 Spring core로 합류하면서 기본적으로 제공한다.
추가적으로 비동기 메서드에서 @Retryable을 사용할 경우 자동으로 Reactor 라이브러리의 내장 기능으로 동작하도록 제공된다.
API Versioning
API를 버전 별로 동작할 수 있도록 해준다.
자세한 사용 방법은 공식 블로그를 참조한다.
JDK 25 LTS
이번 업데이트에서 가장 대두되는 것은 jdk 21에서 추가된 가상 스레드와 ThreadLocal 조합에서의 메모리 문제를 해결하는 JEP 506: Scoped Values 와 JEP 505: Structured Concurrency (Fifth Preview) 라고 생각한다.
가상 스레드를 통해 한정된 OS 스레드를 JVM 객체 단위의 스레드로 변경하여 GC에 의한 메모리 최적화 방식을 통해 스레드 수에 대한 제약이 풀리는 듯 했다.
하지만 각 스레드에 ThreadLocal 데이터를 ThreadLocalMap이라는 독립적인 메모리 공간에 저장되기 때문에 스레드 수에 따른 메모리 제약이 동일하게 발생한다.
이를 해결하기 위해 ScopedValues라는 개념이 도입됐다.
ThreadLocal에 비해 불변이며 스레드 동작 중에만 스코프 값을 읽을 수 있도록 해준다.
또한 개별 Map구조를 가지는 것이 아닌 하나의 ScopedValue에서 관리하여 이를 참조하도록 한다.
그리고 이러한 가상 스레드와 동시성 프로그래밍을 위한 프로그래밍 패러다임을 Structured Concurrency라고 정의한다.
참조
