2025년 11월, Spring Framework 7.0Spring Boot 4.0이 정식 출시되었다. Jakarta EE 11 전면 채택, 내장 레질리언스 패턴, GraalVM 24 네이티브 이미지 완전 지원, Micrometer 2 기반 통합 관찰성 등 대규모 변경이 이루어졌다. 기존 Spring Boot 3.x 프로젝트를 4.0으로 마이그레이션하는 방법을 실전 코드와 함께 살펴보자.

주요 변경사항 요약

  • Jakarta EE 11 - Servlet 6.1, JPA 3.2, Bean Validation 3.1로 업그레이드
  • Java 17 최소 요구 - Java 21 권장, Virtual Threads 기본 지원 강화
  • 내장 레질리언스 - @Retryable, @ConcurrencyLimit 등 외부 라이브러리 없이 사용
  • GraalVM 24 네이티브 - AOT 처리 개선, 빌드 시간 및 메모리 사용량 대폭 감소
  • Micrometer 2 + OpenTelemetry - 트레이싱, 로그, 메트릭 통합 스타터
  • 구조화 로깅 기본 지원 - ECS, GELF, Logstash 형식 내장

build.gradle 마이그레이션

// Spring Boot 3.x (이전)
plugins {
    id 'org.springframework.boot' version '3.3.5'
    id 'io.spring.dependency-management' version '1.1.4'
    id 'java'
}

java {
    sourceCompatibility = '17'
}

// Spring Boot 4.0 (이후)
plugins {
    id 'org.springframework.boot' version '4.0.2'
    id 'io.spring.dependency-management' version '1.2.0'
    id 'java'
}

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(21)
    }
}

dependencies {
    // Jakarta EE 11 의존성 자동 관리
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    
    // 새로운 관찰성 스타터
    implementation 'org.springframework.boot:spring-boot-starter-opentelemetry'
    
    // 새로운 레질리언스 스타터
    implementation 'org.springframework.boot:spring-boot-starter-resilience'
}

내장 레질리언스 패턴

기존에는 Resilience4j 같은 외부 라이브러리가 필요했지만, Spring Framework 7부터는 프레임워크 자체에 레질리언스 어노테이션이 포함되었다.

@Retryable - 자동 재시도

import org.springframework.resilience.annotation.Retryable;
import org.springframework.resilience.annotation.EnableResilientMethods;

@Configuration
@EnableResilientMethods
public class ResilienceConfig {
}

@Service
public class ExternalApiService {

    // 최대 3회 재시도, 지수 백오프
    @Retryable(
        maxAttempts = 3,
        backoff = @Backoff(delay = 1000, multiplier = 2.0),
        retryFor = {IOException.class, TimeoutException.class},
        noRetryFor = {IllegalArgumentException.class}
    )
    public ApiResponse callExternalApi(String endpoint) {
        return restClient.get()
            .uri(endpoint)
            .retrieve()
            .body(ApiResponse.class);
    }
    
    // 재시도 실패 시 폴백
    @Retryable(maxAttempts = 3, fallbackMethod = "getCachedData")
    public Data fetchData(String key) {
        return externalService.getData(key);
    }
    
    public Data getCachedData(String key, Exception ex) {
        log.warn("폴백 실행: {}", ex.getMessage());
        return cacheService.get(key);
    }
}

@ConcurrencyLimit - 동시성 제어

@Service
public class ResourceIntensiveService {

    // 동시 실행 최대 10개로 제한
    @ConcurrencyLimit(permits = 10)
    public ProcessResult heavyProcessing(InputData data) {
        // CPU 집약적 작업
        return process(data);
    }
    
    // 세마포어 + 타임아웃
    @ConcurrencyLimit(
        permits = 5,
        timeout = 5000,  // 5초 대기 후 거부
        rejectedHandler = "handleRejection"
    )
    public Report generateReport(ReportRequest request) {
        return reportGenerator.generate(request);
    }
    
    public Report handleRejection(ReportRequest request, ConcurrencyLimitException ex) {
        return Report.queued(request.getId());
    }
}

구조화 로깅 설정

Spring Boot 4.0에서는 구조화 로깅이 내장되어 있어 설정만으로 바로 사용할 수 있다.

# application.yml
logging:
  structured:
    format:
      console: ecs      # Elastic Common Schema
      file: logstash     # Logstash JSON 형식
  
  # 파일 출력 설정
  file:
    name: /var/log/app/application.log
    max-size: 100MB
    max-history: 30

# 또는 GELF 형식
logging:
  structured:
    format:
      console: gelf
    gelf:
      host: my-app-server
      service: user-service

구조화 로깅 출력 예시

// ECS 형식 출력
{
  "@timestamp": "2026-04-05T14:30:00.123Z",
  "log.level": "INFO",
  "message": "사용자 로그인 성공",
  "service.name": "user-service",
  "trace.id": "abc123def456",
  "span.id": "789ghi",
  "user.id": "12345",
  "ecs.version": "1.2.0"
}

OpenTelemetry 통합

# application.yml - OpenTelemetry 설정
management:
  opentelemetry:
    enabled: true
    exporter:
      otlp:
        endpoint: http://otel-collector:4317
        protocol: grpc
    resource:
      attributes:
        service.name: my-service
        service.version: 1.0.0
        deployment.environment: production
  
  tracing:
    sampling:
      probability: 0.1  # 10% 샘플링
  
  metrics:
    export:
      otlp:
        enabled: true
// 커스텀 스팬 생성
import io.micrometer.tracing.Tracer;
import io.micrometer.tracing.ScopedSpan;

@Service
public class OrderService {
    
    private final Tracer tracer;
    
    public Order createOrder(OrderRequest request) {
        ScopedSpan span = tracer.startScopedSpan("order.create");
        try {
            span.tag("order.type", request.getType());
            span.tag("order.items.count", 
                     String.valueOf(request.getItems().size()));
            
            Order order = processOrder(request);
            span.tag("order.id", order.getId());
            
            return order;
        } catch (Exception ex) {
            span.error(ex);
            throw ex;
        } finally {
            span.end();
        }
    }
}

GraalVM 네이티브 이미지 빌드

# Gradle 네이티브 빌드
./gradlew nativeCompile

# 또는 Buildpacks 사용
./gradlew bootBuildImage \
  --imageName=myapp:native \
  -Pnative

# 네이티브 실행
./build/native/nativeCompile/myapp
# 시작 시간: ~50ms (JVM: ~2000ms)
# 메모리 사용량: ~50MB (JVM: ~250MB)

마이그레이션 체크리스트

  1. Java 버전을 21 이상으로 업그레이드
  2. build.gradle 또는 pom.xml의 Spring Boot 버전을 4.0.x로 변경
  3. javax.* 패키지가 남아있다면 jakarta.*로 변경 (3.x에서 이미 완료했어야 함)
  4. Resilience4j를 사용 중이라면 내장 @Retryable로 전환 검토
  5. Micrometer 1.x 커스텀 메트릭을 Micrometer 2.x API로 업데이트
  6. Spring Security 설정에서 deprecated 메서드 제거
  7. application.properties/yml의 변경된 속성명 확인 (spring-boot-properties-migrator 활용)
  8. 테스트 실행 및 통합 테스트 검증