JDK 21, Spring Boot 3.4 버전업 가이드

이동엽·2025년 1월 20일
1

spring

목록 보기
22/22
post-thumbnail

1. 개요

JDK 21, Spring Boot 3.4로 구성된 API Server를 버전업하기 전, 주요 변화를 파악합니다.

아래 내용은 JDK17, Spring Boot 3.0 이상으로 버전업을 진행했다는 가정 하에 설명합니다.

JDK 11, Spring Boot 2.7 이하라면, Spring Boot 3.0 Migration Guide를 참고하시길 바랍니다.

버전업 목표

구분AS-ISTO-BE
JDK1721
Spring Boot3.0.133.4.x
Spring Cloud2022.0.4미정. (2023.0.5 / 2024.0.0)

2. JDK 버전업 주요 내용

JDK 17 ~ 21까지의 총 주요 변화 및 기능을 설명하며, 각 버전별 세부 내용을 생략합니다.


2-1. What’s New

Virtual Threads

  • 가상 스레드란 기존의 전통적인 Java 스레드에 더하여 새롭게 추가되는 경량 스레드
  • OS 스레드를 그대로 사용하지 않고 JVM 자체 내부 스케줄링을 통해서 사용할 수 있는 경량의 스레드를 제공

플랫폼 스레드란?

  • 자바에서 스레드(java.lang.Thread)라고 부르는 OS에서 생성한 스레드를 매핑해 JVM에서 사용
  • OS에 의해 스케줄링되기 때문에 스레드 간 전환을 위한 문맥 교환이 발생
  • 플랫폼 스레드를 생성하는 것이 OS에도 스레드를 하나 생성하는 것이므로 비용 발생 → 스레드 풀을 사용

가상 스레드란?

  • 가상 스레드는 OS의 스레드와 1:1로 대응되지 않는다.
    • 위에서 플랫폼 스레드라고 부르던 것은 이제 캐리어 스레드라고 부른다.
  • 캐리어 스레드는 포크조인 풀 안에 Worker Thread로 생성되어 스케줄링 되고, 각 Worker Thread들은 큐를 가진다. 이때 Task를 스케줄링하는데 가상 스레드 자체가 각각의 Task가 되어 큐에 들어가게 된다.
  • 가상 스레드는 OS의 스레드와 대응되는 개념도 아니고, JVM에서 직접 스레드를 생성하기 때문에 생성 비용이 비싸지도 않다. 또한 크기가 자동으로 조절되기 때문에 스레드 풀의 개수를 관리할 필요도 없다.

기존 코드와의 호환성

  • 부모 타입인 BaseVirtualThread는 Thread 클래스를 상속 → 기존 Thread 구현의 변경 없이 사용 가능
  • Spring Boot Starter Web을 사용시 3.0버전 이상에서는 Embedded Tomcat을 사용한다.
    • 이때, 가상 스레드를 사용하기 위해서는 아래와 같이 설정하면 된다.
    • 다만 가상 스레드 특성상, 로그에 스레드 이름이 찍히지 않는 점을 알아두자.

Spring Boot 3.2.0 이하일 경우

// Web Request를 처리하는 Tomcat이 Virtual Thread를 사용하여 유입된 요청을 처리하도록 함
@Bean
public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
    return protocolHandler -> protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
}

// Async Task에 Virtual Thread 사용
@Bean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
public AsyncTaskExecutor asyncTaskExecutor() {
    return new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
}

Spring Boot 3.2.0 이후

spring:
  threads:
    virtual:
      enabled: true  # 가상 스레드를 활성화

가상 스레드에 대한 기본 개념 및 성능 비교는 아래 기술 블로그들로 대체한다.


Structured Concurrency

  • 구조화된 동시성을 위한 API를 도입하여 멀티스레드 프로그래밍을 단순화
  • 기본적으로 가상 스레드를 사용

기존 동시성을 사용한 코드

Future<Shelter> shelter;
Future<List<Dog>> dogs;

// ThreadPool의 크기가 3인 Executor 생성
try (ExecutorService executorService = Executors.newFixedThreadPool(3)) {

		// get shelter
    shelter = executorService.submit(this::getShelter);
		// get dogs
    dogs = executorService.submit(this::getDogs);

    Shelter theShelter = shelter.get();   // Join the shelter
    List<Dog> theDogs = dogs.get();  // Join the dogs
    Response response = new Response(theShelter, theDogs);
} catch (ExecutionException | InterruptedException e) {
    throw new RuntimeException(e);
}

위 코드에서 getShelter()가 실행되는 동안 getDocs()에서 예외가 발생할 경우의 플로우

Structured Concurrency를 사용

// ShutdownOnFailure()은 해당 scope에서 문제가 발생할 경우 하위 작업을 모두 종료하는 생성자
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    Future<Shelter> shelter = scope.fork(this::getShelter);
    Future<List<Dog>> dogs = scope.fork(this::getDogs);

    scope.join();
    Response response = new Response(shelter.resultNow(), dogs.resultNow());
    // ...
}

기타 사용법

// 예외를 전파
scope.throwIfFailed(e -> new APIException(400, "ERROR_CODE", "ERROR_MESSAGE"));

// 마감일 설정
scope.joinUntil(Instant.now().plusSeconds(1));

Sequenced Collections

  • 정의된 만남 순서로 컬렉션을 나타내는 새로운 인터페이스를 도입
  • 이러한 각 컬렉션에는 잘 정의된 첫 번째 요소, 두 번째 요소 등을 거쳐 마지막 요소까지 포함
  • 또한 첫 번째 요소와 마지막 요소에 액세스하고 해당 요소를 역순으로 처리하기 위한 통일된 API를 제공
interface SequencedCollection<E> extends Collection<E> {
    // new method
    SequencedCollection<E> reversed();

    // methods promoted from Deque
    void addFirst(E);
    void addLast(E);
    E getFirst();
    E getLast();
    E removeFirst();
    E removeLast();
}

Unnamed Patterns and Variables

  • 구성 요소의 이름이나 유형을 명시하지 않고 이름 없는 패턴 및 변수로, 밑줄 문자(_)로 표시
public class UnnamedPatternExample {
    public static void main(String[] args) {
        final int total = 0;
        final int LIMIT = 3;
        final List<String> strings = List.of("1", "2", "3");
        
        // Unnamed Variable
        for (String _ : strings) {
            if (total < LIMIT) {
                System.out.println("UnnamedPatternExample.main");
            }
        }
    }
}

Scoped Values

  • 이 기능은 스코프 값을 도입하여 스레드 로컬 변수와 비슷한 동작을 지원
  • 스코프 값은 특정 범위 내에서만 유효하며, 범위를 벗어나면 자동으로 제거됨.
    • → 이는 스레드 간 상태 공유를 줄이고, 코드의 안정성과 격리성을 향상시키는 데 도움이 됩니다.
class Framework {
    private final static ScopedValue<FrameworkContext> CONTEXT
                        = ScopedValue.newInstance();   // (1)

    void serve(Request request, Response response) {
        var context = createContext(request);
        ScopedValue.where(CONTEXT, context)            // (2)
                   .run(() -> Application.handle(request, response));
    }

    public PersistedObject readKey(String key) {
        var context = CONTEXT.get();            // (3)
        var db = getDBConnection(context);
        db.readKey(key);
    }

    ...
}

2-2. 메모리 사용량 개선

힙 메모리

네이티브 메모리


2-3. 성능 개선

GC(가비지 컬렉션) 성능

동시성 처리


3. Spring Boot 버전업 주요 내용

주요 변화로 정리된 내용은 개인 관심사 및 기술 스택이 반영되어있습니다.


3-1. Spring Boot 3.1 Release Note

Spring Boot 3.1 upgrades to Hibernate 6.2

Spring Kafka Retry Topic Auto-configuration Enabled

  • spring.kafka.retry.topic.enabled: true

Spring for GraphQL

  • Enable Exception Handling (@GraphQlExceptionHandler with @ControllerAdvice)

Dependency Upgrades


3-2. Spring Boot 3.2 Release Note

Logging Correlation IDs

  • management.tracing.enabled=false 로 설정하지 않는 한, SpanId, TraceId를 로그에 기록
logging:
  pattern:
    correlation: "[${spring.application.name:},%X{traceId:-},%X{spanId:-}] "
  include-application-name: false

RestClient Support

  • Blocking API with Similar to design to WebClient

Support for Virtual Threads

  • You need to run on Java 21!!
  • 활성화 설정 : spring.threads.virtual.enabled=true

Dependency Upgrades

  • Spring Framework 6.1
  • Spring Retry 2.0
  • Spring Security 6.2
  • Log4j 2.21
  • Micrometer Tracing 1.2

3-3. Spring Boot 3.3 Release Note

Prometheus Client 1.x

  • Spring Boot 3.3 includes support for the Prometheus Client 1.x.
  • 메트릭 이름이 변경되는 등의 주요 변화가 존재 (추가 조사 필요)
  • 기존의 Prometheus 0.x 클라이언트를 계속 사용하고 싶을 경우
    • io.micrometer:micrometer-registry-prometheus 의존성을 제거 후io.micrometer:micrometer-registry-prometheus-simpleclient 를 추가
    • Spring Boot는 simpleclient에 대한 자동 구성을 제공하지만, deprecated되어 Spring Boot 3.5.0 버전에서 제거될 예정.
  • 관련 가이드 : Mirometer Migration Guide

Virtual Threads

  • If a AsyncTaskExecutor is available in the context, it is now registered on the websocket ChannelRegistration.
  • WebSocket을 이용할 경우에도 가상 스레드를 사용할 수 있도록 동작.

Dependency Upgrades


3-4. Spring Boot 3.4 Release Note

RestClient and RestTemplate

  • RestClient와 RestTemplate의 자동 설정 지원 추가
  • 기본 클라이언트 변경
    • AS-IS : Simple JDK HttpURLConnection (SimpleClientHttpRequestFactory)
    • TO-BE : JDK HttpClient (JdkClientHttpRequestFactory)
  • 특정 클라이언트를 선택하는 설정 옵션 추가
    • spring.http.client.factor=??
    • 값으로는 http-components, jetty, reactor, jdk, simple 이 가능

Bean Validation of Configuration Properties

  • AS-IS : 중첩된 속성에 자동으로 @Valid 가 적용되어 검증이 동작.

    @Validated
    @ConfigurationProperties(prefix = "app")
    public class AppProperties {
    
        @NotBlank
        private String name;
        @NotNull
        private DatabaseProperties database;  // 중첩된 속성
    
        public static class DatabaseProperties {
            @NotBlank
            private String url;
            @NotBlank
            private String username;
        }
    }
  • TO-BE : @Valid 를 추가해야만 검증이 이루어지도록 제어 가능

    @Validated
    @ConfigurationProperties(prefix = "app")
    public class AppProperties {
    
        @NotBlank
        private String name;
    
        @Valid  // 중첩된 속성에 대해 검증을 전파하기 위해 추가
        @NotNull
        private DatabaseProperties database;  // 중첩된 속성
    
        public static class DatabaseProperties {
            @NotBlank
            private String url;
            @NotBlank
            private String username;
        }
    }

Graceful Shutdown

  • Graceful Shutdown 속성의 기본 값 변경
    • AS-IS : immediate
    • TO-BE :graceful
  • 따라서즉시 종료를 원할 경우 설정 필요 ⇒ server.shutdown=immediate

Deprecation of @MockBean and @SpyBean

  • Mockito를 이용한 테스트 작성시 @MockBean , @SpyBean 를 사용할 수 없음.
  • 따라서 @MockitoBean , @MockitoSpyBean 로 교체가 필요.

Virtual Thread

  • 가상 스레드 활성화시, Undertow가 요청을 처리할 때 가상 스레드를 사용

Dependency Upgrades


4. Spring Cloud 버전업 주요 내용

4-1. Spring Cloud 2022.0 Release Notes

Spring Cloud Sleuth

  • Spring Cloud Sleuth는 더 이상 특정 릴리스 트레인에 포함되지 않음.
  • 이 프로젝트의 핵심 기능은 Micrometer Tracing 프로젝트로 이전.

Spring Cloud Gateway

  • Actuator Gateway API 추가 (Github Issue Link)

    • /actuator/gateway/refresh

    • /actuator/gateway/routes

    • /actuator/gateway/routefilters

    • /actuator/gateway/routepredicates

      management.endpoint.gateway.enabled=true # default value
      management.endpoints.web.exposure.include=gateway

Spring Cloud Openfeign

  • feign.autoconfiguration.jackson.enabled 기본값 변경
    • 이 속성이 기본적으로 true로 설정 ⇒ Jackson 자동 설정을 기본적으로 활성화
  • 속성 접두사 변경
    • AS-IS : feign
    • TO-BE : spring.cloud.openfeign

4-2. Spring Cloud 2023.0.0

This release is based upon Spring Boot 3.2.0

Spring Cloud Gateway Server MVC

  • Spring Cloud Gateway와 Spring MVC가 결합된 상태. (Spring Blog)
  • spring-cloud-starter-gateway-mvc
    • Spring Mvc 기반 / Servlet API / Blocking I/O 모델 사용 / Tomcat
  • spring-cloud-starter-gateway
    • Spring Webflux 기반 / Reative API / Non-blocking I/O 모델 사용 / Netty
    • Webflux 기반이기 때문에 SpringMVC기반의 라이브러리와 함께 쓸 경우, 아래 에러 발생
    • “Spring MVC found on classpath, which is incompatible with Spring Cloud Gateway."

Following Modules updated.

  • Spring Cloud Openfeign 4.1.0 (issues)
  • Spring Cloud Config 4.1.0 (issues)
  • Spring Cloud Gateway 4.1.0 (issues)

4-3. Spring Cloud 2023.0.1

Spring Cloud Gateway

  • Gateway Server MVC 지원 for AOT (#3171)
    • AOT( Ahead-of-Time) 컴파일을 지원하는 Gateway Server MVC가 추가
      • AOT는 애플리케이션을 미리 컴파일하여 실행 시 성능을 최적화하는 방식
      • ⇒ AOT를 적용하여 빌드 및 실행 성능을 개선 가능.
  • RouteRefreshListener 비활성화 옵션 추가 (#2958)
    • RouteRefreshListener는 라우팅 정보가 변경될 때 자동으로 리프레시(새로 고침) 되는 기능을 제공
      • 특정 상황에서는 이 자동 새로 고침 기능을 비활성화가 필요.
      • ⇒ 이를 통해 사용자는 필요에 따라 라우팅 정보가 자동으로 갱신되지 않도록 가능.
      • spring.cloud.gateway.route-refresh-listener.enabled=true

Following Modules updated.

  • Spring Cloud Openfeign 4.1.1 (issues)
  • Spring Cloud Gateway 4.1.2 (issues)
  • Spring Cloud Config 4.1.1 (issues)

4-4. Spring Cloud 2023.0.2

영향받는 변경 사항 및 주목 할만한 신규 기능 없음으로 판단.

Following Modules updated.

  • Spring Cloud Openfeign 4.1.2 (issues)
  • Spring Cloud Gateway 4.1.4 (issues)
  • Spring Cloud Config 4.1.2 (issues)

4-5. Spring Cloud 2023.0.3

This release is based on Spring Boot 3.2.7 it primarily contains bugfixes and dependency upgrades.

Following Modules updated.

  • Spring Cloud Openfeign 4.1.3 (issues)
  • Spring Cloud Gateway 4.1.5 (issues)
  • Spring Cloud Config 4.1.3 (issues)

4-6. Spring Cloud 2023.0.4

This release of Spring Cloud is based on Spring Boot 3.2.12 and 3.3.6.

Spring Cloud Gateway

  • Updated to use HttpHeaders#headerSet where appropriate.

    • This change requires Spring Framework 6.1.15+, which is available in Spring Boot 3.2.12+ or 3.3.6+. (3596)
    • 헤더에 값을 변경하는 로직이 최적화됨을 의미
    public class CustomHeaderFilter implements GatewayFilter {
    
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            // 요청의 헤더를 가져오기
            HttpHeaders headers = exchange.getRequest().getHeaders();
    
            // 'headerSet'을 사용하여 커스텀 헤더 추가
            HttpHeaders modifiedHeaders = new HttpHeaders();
            modifiedHeaders.putAll(headers);  // 기존 헤더를 유지
            **modifiedHeaders.set("X-Custom-Gateway-Header", "GatewayHeaderValue");**
    
    				...
    		}
    }

Following Modules updated.

  • Spring Cloud Openfeign 4.1.4 (issues)
  • Spring Cloud Gateway 4.1.6 (issues)
  • Spring Cloud Config 4.1.4 (issues)

4-7. Spring Cloud 2023.0.5

This is mostly a bugfix and dependency upgrade release.

Following Modules updated.

  • Spring Cloud Config 4.1.5 (issues)

4-8. Spring Cloud 2024.0.0

This release is based upon Spring Boot 3.4.0 and Spring Framework 6.2.0.


Adds an enabled flag to a route (#3026)

  • 각 라우트(route)에 대해 enabled 플래그를 추가하여 해당 라우트를 활성화 / 비활성화 가능
  • 특정 라우트를 동적으로 비활성화하고 비즈니스 요구에 맞게 라우트를 조정 가능.
    • → 특정 서비스나 API를 일시적으로 비활성화해야 할 경우 유용

Cache gateway filters to avoid sorting in every single request (#2756)

  • 매번 요청을 처리할 때마다 게이트웨이 필터정렬하지 않도록 캐싱을 적용
    • Spring Cloud Gateway에서 요청이 들어올 때마다 필터를 처리하고 정렬하는 과정이 성능상 비효율적
    • → 필터를 미리 캐시하여 동일한 필터에 대한 중복 정렬을 피하고, 요청을 더 빠르게 처리

Following Modules updated.

  • Spring Cloud Openfeign 4.2.0 (issues)
  • Spring Cloud Gateway 4.2.0 (issues)
  • Spring Cloud Config 4.2.0 (issues)

5. 참고 자료

profile
백엔드 개발자로 등 따숩고 배 부르게 되는 그 날까지

0개의 댓글