
시니어 개발자로서 실무에서 가장 골치 아픈 점은 단순히 "버전을 올리는 것"이 아니라, 그 과정에서 얽히고설킨 의존성 파편화를 해결하는 과정이죠.
특히 Spring Boot 4 / Resilience4j 3.0(Java 21+) 환경으로 전환할 때, Kotlin + WebFlux 스택을 사용하는 프로젝트라면 반드시 마주하게 될 '지뢰'들이 있습니다.
제가 직접 겪어본 경험을 바탕으로, 구체적인 충돌 시나리오와 해결 코드를 정리해 보았습니다.
Spring Boot 4는 Jackson 3를 베이스로 합니다. 하지만 많은 서드파티 라이브러리는 여전히 Jackson 2를 참조하고 있습니다.
충돌 시나리오: Resilience4j의 Registry 정보를 JSON으로 직렬화하여 Actuator로 노출할 때, 내부적으로 Jackson 2 클래스를 찾으려다 NoClassDefFoundError가 발생합니다.
해결책: 프로젝트의 build.gradle.kts에서 전역적으로 의존성 버전을 강제하거나, 신규 resilience4j-spring-boot4 모듈을 명확히 지정해야 합니다.
Spring Boot 4는 Java 17/21을 최소 사양으로 하며, Jakarta EE 10/11을 따릅니다.
javax.validation.*을 참조하고 있다면, Boot 4의 jakarta.validation.*과 충돌하여 검증 로직이 아예 작동하지 않거나 컴파일 오류가 발생합니다.Kotlin과 WebFlux를 쓴다면, 단순 동기 방식이 아닌 Coroutine과의 궁합이 가장 중요합니다. Resilience4j 3.0은 Java 21의 Virtual Threads를 지원하기 때문에, 이를 활용한 논블로킹 설정이 핵심입니다.
build.gradle.kts 설정기존의 resilience4j-spring-boot3 대신 boot4 전용 모듈과 Kotlin 코루틴 지원 모듈을 추가해야 합니다.
dependencies {
// Spring Boot 4 & WebFlux
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
// Resilience4j 3.x (Boot 4 전용 모듈)
implementation("io.github.resilience4j:resilience4j-spring-boot4:3.x.x") // 최신 마일스톤 확인 필요
implementation("io.github.resilience4j:resilience4j-kotlin") // 코루틴 연동 필수
implementation("io.github.resilience4j:resilience4j-reactor") // WebFlux 연동용
// Java 21 Virtual Threads 활성화 (Boot 3.2+ / Boot 4 필수)
implementation("org.springframework.boot:spring-boot-starter-actuator")
}
시니어라면 비즈니스 로직과 인프라 설정을 깔끔하게 분리해야겠죠.
Decorators를 사용하는 것보다 Kotlin의 확장 함수를 쓰는 것이 훨씬 가독성이 좋습니다.
@Service
class PaymentService(
private val paymentClient: WebClient,
private val circuitBreakerRegistry: CircuitBreakerRegistry
) {
private val circuitBreaker = circuitBreakerRegistry.circuitBreaker("paymentService")
suspend fun processPayment(orderId: String): PaymentResponse {
// 코루틴 환경에서 Circuit Breaker 실행
return circuitBreaker.executeSuspendFunction {
paymentClient.post()
.uri("/v1/payments")
.bodyValue(PaymentRequest(orderId))
.retrieve()
.awaitBody<PaymentResponse>() // WebFlux를 코루틴으로 전환
}
}
}
application.yml 설정Spring Boot 4의 핵심인 Virtual Thread를 Resilience4j와 연결하는 설정입니다.
spring:
threads:
virtual:
enabled: true # Java 21+ Virtual Thread 활성화
resilience4j:
circuitbreaker:
configs:
default:
registerHealthIndicator: true
slidingWindowSize: 10
# Virtual Thread 환경에서는 Context switching 비용이 낮으므로
# 타임아웃이나 대기 스레드 설정을 좀 더 공격적으로 가져갈 수 있습니다.
waitDurationInOpenState: 5s
failureRateThreshold: 50
instances:
paymentService:
baseConfig: default
실제로 모듈을 바꿨는데도 작동이 안 된다면 다음 두 가지를 먼저 체크하세요.
spring.factories vs org.springframework.boot.autoconfigure.AutoConfiguration.imports:
Spring Boot 4는 기존의 spring.factories 방식의 자동 설정 등록 대신 새로운 .imports 파일 형식을 선호합니다.
만약 커스텀 라이브러리를 운영 중이라면 이 파일 위치를 옮겨야 합니다.
Micrometer Metrics 충돌:
GitHub 이슈에서도 언급되었듯, Boot 4에서는 Micrometer의 빈 등록 순서가 달라졌습니다. Metrics가 프로메테우스로 전송되지 않는다면 ObservationRegistry가 제대로 주입되었는지 확인해야 합니다.
오픈소스 라이브러리의 버전업은 단순히 기능 추가가 아니라 '생태계의 변화'를 수용하는 과정임을 다시금 느꼈습니다.
특히 이번 Boot 4 전환은 Jakarta EE 11과 Jackson 3라는 큰 산이 있어 의존성 트리를 파헤치는 시간이 많았네요.
화려한 기술보다는 "실패했을 때 사용자에게 어떤 메시지를 줄 것인가"와 "운영 환경에서 모니터링 수치가 제대로 찍히는가"를 먼저 챙기는 것이 결국 가장 빠른 지름길이라는 점을 다시금 배웁니다.