2022년 Spring Boot 3와 Spring Framework 6이 Java 17 기본 요구사항과 javax → jakarta 마이그레이션을 가져오면서 생태계에 큰 변화가 있었다. 2025년 11월, Spring Boot 4.0.0이 공식 출시되었다. Maven Central에서 이미 사용 가능하다.
이번 릴리스에서 가장 크게 달라진 점을 먼저 이야기하자면:
- API 버전 관리가 프레임워크 차원에서 지원된다 — 더 이상 URL에
/v1/,/v2/를 직접 넣거나 커스텀 헤더를 만들 필요가 없다. - Resilience 어노테이션이 내장된다 —
@Retryable,@ConcurrencyLimit을 별도 라이브러리 없이 바로 쓸 수 있다. - 모듈화가 대폭 개선된다 — 빌드 속도와 네이티브 이미지 생성이 빨라진다.
이 세 가지가 체감하기 가장 큰 변화다. 하나씩 자세히 보자.
기본 요구사항
새 기능을 보기 전에 기본 요구사항부터 짚고 넘어가자.
- Java 17은 여전히 최소 요구사항이다. Java 25는 First Class 지원이며, Java 21도 권장된다. Virtual Threads와 Project Leiden의 AOT 지원을 활용하려면.
- Jakarta EE 11이 완전히 채택되었다. Servlet 6.1, JPA 3.2, Bean Validation 3.1.
- Jackson 3와 Kotlin 2.2+ 지원으로 최신 언어 기능과 데이터 직렬화 방식에 맞춰 개발 효율성이 높아졌다.
Spring Boot 4의 변화
GraalVM 네이티브 이미지 개선
Spring Boot 4는 GraalVM 24와 완전히 정렬되어 있다. AOT(Ahead-of-Time) 처리가 개선되어 빌드 시간이 줄고 시작 메모리 사용량도 감소했다.
Spring Data는 AOT Repositories를 도입했다. 쿼리 메서드가 AOT 처리 과정에서 소스 코드로 변환되어 애플리케이션과 함께 컴파일된다.
Project Leiden을 통한 빌드 타임 최적화도 눈여겨볼 만하다. 훈련 실행을 통해 얻은 메서드 사용 데이터를 기반으로 정적 컴파일을 수행하면:
- JVM의 peak 성능 도달 시간이 약 8초로 단축
- 일부 예제에서 시작 시간이 1.29초로 단축 (4배 이상 향상)
- 대형 애플리케이션에서 JIT 컴파일 과정 없이 높은 초기 응답 속도 확보
클라우드와 서버리스 환경에서 특히 유용하다.
Micrometer 2 + OpenTelemetry 통합
클라우드 네이티브 앱에서 관측 가능성(Observability)은 필수다. Spring Boot 4는 Micrometer 2로 업그레이드되고 OpenTelemetry 스타터가 통합된다. traces, logs, metrics가 매끄럽게 연동된다.
모듈화 개선
Spring Boot 4의 첫 번째 마일스톤 중 하나가 자체 코드베이스의 모듈화 리팩토링이었다.
Spring Boot 3에서는 핵심 모듈들이 큰 아티팩트에 묶여 있었다. 편하긴 했지만 의존성 관리가 어렵고 클래스패스 스캔 오버헤드가 있었으며 네이티브 이미지 크기도 컸다.
Spring Boot 4부터는 auto-configuration과 지원 코드가 더 작고 집중된 모듈로 분리되었다:
- 빠른 빌드와 네이티브 이미지 생성 — GraalVM AOT가 불필요한 힌트와 메타데이터를 처리할 필요가 없다.
- 깔끔한 의존성 관리 — Micrometer, OpenTelemetry 같은 선택적 통합이 별도 모듈로 분리.
- 유지보수성 향상 — 모듈이 기능과 직접 매핑된다.
starter 의존성을 쓰면 바뀌는 게 없다. 그냥 spring-boot-starter-data-jpa를 추가하면 된다. 차이는 내부에 있다.
@ConfigurationPropertiesSource 어노테이션
모듈화를 위한 새 어노테이션이다. 런타임 동작은 바꾸지 않고 빌드 타임에 spring-boot-configuration-processor를 위한 힌트로 작동한다.
모듈 프로젝트에서 다른 모듈에 있는 중첩 타입이나 베이스 클래스를 참조할 때 메타데이터가 불완전할 수 있다. 이 어노테이션으로 해결된다.
Spring Data 4.0 쿼리 성능
Spring Data 4.0에서는 문자열 기반 쿼리 생성이 기본 지원된다. 기존 Criteria API 방식보다 약 6배 빠르다. 쿼리 성능 향상을 위해 별다른 코드 변경 없이 업그레이드만으로도 효과를 볼 수 있다.
AOT Repository 덕분에 쿼리 메서드가 정적으로 생성되어:
- 디버깅 시 내부 코드를 직접 확인하기 쉽다
- JPA 사용 시 초기 로딩 속도가 개선된다
- 메모리 사용량도 최적화된다
Spring Framework 7의 변화
API 버전 관리 (가장 기다렸던 기능)
드디어 프레임워크 차원의 API 버전 관리가 지원된다. 예전에는 URL 경로 컨벤션, 커스텀 헤더, 미디어 타입 등을 직접 구현해야 했다.
이제는 version 속성을 지정하면 된다:
@RestController
@RequestMapping("/hello")
public class HelloWorldController {
@GetMapping(version = "1", produces = MediaType.TEXT_PLAIN_VALUE)
public String sayHelloV1() {
return "Hello World";
}
@GetMapping(version = "2", produces = MediaType.TEXT_PLAIN_VALUE)
public String sayHelloV2() {
return "Hi World";
}
}
컨트롤러 레벨에서도 지정할 수 있다:
@RestController
@RequestMapping(path = "/hello", version = "3")
public class HelloWorldV3Controller {
@GetMapping(produces = MediaType.TEXT_PLAIN_VALUE)
public String sayHello() {
return "Hey World";
}
}
매핑 전략은 4가지 중 선택할 수 있다:
| 전략 | 예시 |
|---|---|
| 경로 기반 | /api/v1/hello vs /api/v2/hello |
| 쿼리 파라미터 | /hello?version=1 vs /hello?version=2 |
| 요청 헤더 | X-API-Version: 1 vs X-API-Version: 2 |
| 미디어 타입 | Accept: application/json; version=1 |
경로 기반 설정 예시:
@Configuration
public class ApiConfig implements WebMvcConfigurer {
@Override
public void configureApiVersioning(ApiVersionConfigurer configurer) {
configurer.usePathSegment(1);
}
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("/api/v{version}",
HandlerTypePredicate.forAnnotation(RestController.class));
}
}
Resilience 어노테이션
별도 라이브러리 없이 재시도와 동시성 제한을 어노테이션으로 처리할 수 있다:
@HttpExchange("/api")
public interface MyApiClient {
@GetExchange("/data")
@Retryable(maxAttempts = 3, delay = 100, multiplier = 2, maxDelay = 1000)
@ConcurrencyLimit(3)
String getData();
}
@Retryable: 실패 시 최대 3회 재시도, 100ms부터 시작해 2배씩 증가, 최대 1000ms까지@ConcurrencyLimit: 동시에 3개 스레드만 실행 가능. Virtual Threads 환경에서 유용
활성화하려면:
@Configuration
@EnableResilientMethods
public class ResilienceConfig {
}
선언적 HTTP 클라이언트 개선
Spring Framework 6에서 도입된 HTTP 인터페이스가 더 편해졌다. OpenFeign 같은 외부 라이브러리 없이 선언적 클라이언트를 만들 수 있다:
@HttpExchange("/api")
public interface ExternalApiClient {
@GetExchange("/greetings?random")
String getRandomGreeting();
}
@Configuration
public class HttpClientConfig {
@Bean
public ExternalApiClient externalApiClient() {
WebClient webClient = WebClient.builder()
.baseUrl("https://api.example.com")
.build();
HttpServiceProxyFactory factory = HttpServiceProxyFactory
.builder(WebClientAdapter.forClient(webClient))
.build();
return factory.createClient(ExternalApiClient.class);
}
}
HTTP 서비스 자동 구성도 추가되었다. 기존에는 외부 API를 호출할 때마다 프록시 객체 생성과 설정 작업을 반복해야 했다. 이제 HTTP 서비스 인터페이스를 자동으로 인식하고 빈으로 등록한다:
- 반복 코드 제거 — 매번 설정 코드를 작성할 필요 없음
- 서비스 그룹 구성 — 여러 HTTP 클라이언트를 그룹으로 관리
- 클라이언트 세부 설정 — 연결/읽기 시간 제한 등 세밀한 설정 가능
- OAuth 인증 간소화 — 주석만으로 인증 설정 처리
테스트 개선
Test Context Pausing이 도입되었다. 긴 통합 테스트에서 유휴 상태일 때 컨텍스트를 일시 중지해 메모리를 절약하고 실행 속도를 높인다.
새로운 RestTestClient도 추가되었다. WebTestClient와 비슷하지만 Reactive 의존성 없이 REST 엔드포인트를 테스트할 수 있다:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ApiIntegrationTest {
RestTestClient client;
@BeforeEach
void setUp(WebApplicationContext context) {
client = RestTestClient.bindToApplicationContext(context).build();
}
@Test
void shouldFetchHello() {
client.get()
.uri("/api/hello")
.exchange()
.expectStatus().isOk()
.expectBody(String.class)
.consumeWith(response -> assertThat(response.getResponseBody()).contains("hello"));
}
}
여러 TaskDecorator 빈 지원
예전에는 TaskDecorator를 하나만 등록할 수 있어서 여러 관심사를 적용하려면 직접 합성해야 했다. 이제 여러 TaskDecorator 빈을 등록하면 자동으로 체인으로 합성된다:
@Configuration
public class TaskDecoratorConfiguration {
@Bean
@Order(1)
TaskDecorator measuringDecorator() {
return runnable -> () -> {
long start = System.currentTimeMillis();
try {
runnable.run();
} finally {
log.info("Finished in {}ms", System.currentTimeMillis() - start);
}
};
}
@Bean
@Order(2)
TaskDecorator loggingDecorator() {
return runnable -> () -> {
log.info("Starting task");
try {
runnable.run();
} finally {
log.info("Finished task");
}
};
}
}
JSpecify로 Null Safety
자바 생태계에는 @Nonnull, @Nullable, @NotNull 등 다양한 Nullability 어노테이션이 난립해 있었다. Spring Framework 7은 JSpecify를 표준으로 채택했다:
@Override
public void configureApiVersioning(@NonNull ApiVersionConfigurer configurer) {
configurer.usePathSegment(1);
}
IDE 지원과 Kotlin 연동이 개선되어 NullPointerException 위험이 줄어든다.
제거되는 것들
현대화와 함께 정리도 이뤄진다:
| 제거 대상 | 대체 |
|---|---|
javax.* 패키지 |
Jakarta EE 11만 지원 |
| Jackson 2.x | Jackson 3.x 필요 |
| Spring JCL | Apache Commons Logging |
| JUnit 4 | JUnit Jupiter 6만 지원 |
마이그레이션 팁
- Java 21로 업그레이드 — Virtual Threads 활용을 위해
- javax → jakarta 확인 — 남아있는 javax 참조 제거
- JUnit 4 테스트 마이그레이션 — JUnit 5로
- Jackson 3.x 호환성 확인
마무리
Spring Boot 4와 Spring Framework 7은 단순한 증분 업데이트가 아니다. 모던하고 모듈화된 클라우드 네이티브 Java 개발로의 의도적인 진화다.
특히 API 버전 관리, Resilience 어노테이션, 모듈화 개선은 마이그레이션을 고민할 만한 충분한 이유가 된다. 생산성, 성능, 유지보수성 면에서 확실히 이득이다.
참고