Spring Boot 3.4 주요 변경 사항 개요

Spring Boot 3.4는 Java 17 베이스라인을 유지하면서 Java 21/25 가상 스레드와의 통합을 크게 강화했습니다. RestClient 자동 구성 개선, Spring Security OAuth2 업데이트, GraalVM 네이티브 이미지 지원 향상이 이번 버전의 핵심입니다. 이 글에서는 실제 프로덕션 환경에 바로 적용 가능한 코드 예제를 중심으로 3.4의 변경사항을 상세히 설명합니다.

1. RestClient 자동 구성 강화

Spring Boot 3.4는 RestClientRestTemplate을 Reactor Netty의 HttpClient 또는 JDK의 내장 HttpClient를 사용하도록 자동 구성하는 기능이 추가되었습니다.

// Spring Boot 3.0부터 권장되는 RestClient (RestTemplate 대체)
@Configuration
public class HttpClientConfig {

    // Spring Boot 3.4: 자동 구성된 RestClient.Builder 주입
    @Bean
    public RestClient userApiClient(RestClient.Builder builder) {
        return builder
            .baseUrl("https://api.example.com")
            .defaultHeader("Accept", "application/json")
            .requestInterceptor((request, body, execution) -> {
                // 공통 요청 처리 (로깅, 인증 헤더 추가 등)
                log.info("Requesting: {} {}", request.getMethod(), request.getURI());
                return execution.execute(request, body);
            })
            .build();
    }
}

// RestClient 사용 예시
@Service
public class UserService {
    private final RestClient userApiClient;

    public User findById(Long id) {
        return userApiClient.get()
            .uri("/users/{id}", id)
            .retrieve()
            .onStatus(HttpStatusCode::is4xxClientError, (request, response) -> {
                throw new UserNotFoundException("User not found: " + id);
            })
            .body(User.class);
    }

    public List findAll() {
        return userApiClient.get()
            .uri("/users")
            .retrieve()
            .body(new ParameterizedTypeReference>() {});
    }

    // 비동기 요청 (Exchange)
    public ResponseEntity findByIdWithHeaders(Long id) {
        return userApiClient.get()
            .uri("/users/{id}", id)
            .exchange((request, response) -> {
                return ResponseEntity.status(response.getStatusCode())
                    .headers(response.getHeaders())
                    .body(response.bodyTo(User.class));
            });
    }
}

2. Spring Security 6.4/6.5 주요 업데이트

OAuth2 Login 개선

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    // Spring Boot 3.4: OAuth2AuthorizationRequestResolver를 @Bean으로 등록 가능
    @Bean
    public OAuth2AuthorizationRequestResolver customRequestResolver(
            ClientRegistrationRepository repo) {
        var defaultResolver = new DefaultOAuth2AuthorizationRequestResolver(
            repo, "/oauth2/authorization");
        // PKCE 강제 적용 (공개 클라이언트)
        defaultResolver.setAuthorizationRequestCustomizer(
            OAuth2AuthorizationRequestCustomizers.withPkce());
        return defaultResolver;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/public/**").permitAll()
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .oauth2Login(oauth2 -> oauth2
                // Bean으로 등록된 resolver 자동 연결
                .authorizationEndpoint(endpoint -> endpoint
                    .authorizationRequestRepository(new HttpSessionOAuth2AuthorizationRequestRepository())
                )
            )
            .logout(logout -> logout
                // Spring Boot 3.4: 로그아웃 시 감사(audit) 이벤트 자동 발행
                .logoutSuccessUrl("/login?logout")
            );
        return http.build();
    }
}

OAuth2Client를 이용한 서비스 간 인증

@Configuration
public class OAuth2RestClientConfig {

    // Spring Boot 3.4: RestClient + OAuth2 자동 토큰 주입
    @Bean
    public RestClient oauth2RestClient(
            RestClient.Builder builder,
            OAuth2AuthorizedClientManager authorizedClientManager) {

        var interceptor = new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);

        return builder
            .baseUrl("https://resource-server.example.com")
            .requestInterceptor(interceptor) // 자동으로 Bearer 토큰 추가
            .build();
    }
}

@Service
public class ResourceService {
    private final RestClient oauth2RestClient;

    public Data fetchProtectedResource() {
        // Authorization: Bearer {token} 헤더가 자동으로 추가됨
        return oauth2RestClient.get()
            .uri("/api/protected-data")
            .retrieve()
            .body(Data.class);
    }
}

3. Virtual Threads 통합 설정

# application.properties (Spring Boot 3.2+)
spring.threads.virtual.enabled=true

# 가상 스레드 활성화 시 Tomcat, Jetty 모두 자동 적용
# @Async 메서드도 가상 스레드로 실행됨
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        // @Async 메서드에 가상 스레드 사용
        return Executors.newVirtualThreadPerTaskExecutor();
    }
}

@Service
public class EmailService {

    @Async
    public CompletableFuture sendWelcomeEmail(String email) {
        // 가상 스레드에서 실행 — I/O 블로킹해도 다른 요청 처리 가능
        emailClient.send(email, "환영합니다!");
        return CompletableFuture.completedFuture(null);
    }
}

4. GraalVM Native Image 개선

Spring Boot 3.4는 GraalVM 네이티브 이미지 지원을 강화했습니다. 특히 Spring Security의 @AuthorizeReturnObject 기능과 @PreAuthorize/@PostAuthorize 콜백이 AOT 환경에서 정상 작동합니다.

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

# Maven으로 네이티브 이미지 빌드
./mvnw -Pnative native:compile

# 실행 (JVM 없이 직접 실행, 시작 시간 ~50ms)
./target/my-application
@Service
public class OrderService {

    // 네이티브 이미지에서도 정상 동작 (Spring Boot 3.4)
    @PreAuthorize("hasRole(ADMIN) or #userId == authentication.principal.id")
    @AuthorizeReturnObject
    public Order findOrder(Long orderId, Long userId) {
        return orderRepository.findById(orderId)
            .orElseThrow(() -> new OrderNotFoundException(orderId));
    }
}

5. Health Groups 개선 및 Actuator

# 커스텀 헬스 그룹 설정
management.endpoint.health.group.readiness.include=db,redis,diskSpace
management.endpoint.health.group.liveness.include=ping

# 추가 경로 매핑 (Spring Boot 3.4 신기능)
management.endpoint.health.group.readiness.additional-path=server:/readyz
management.endpoint.health.group.liveness.additional-path=server:/livez

# 세션 쿠키 분리 설정 (Spring Boot 3.4)
server.servlet.session.cookie.partitioned=true

6. 의존성 버전 주요 변경사항

# build.gradle (Spring Boot 3.4 기준)
plugins {
    id org.springframework.boot version 3.4.0
    id io.spring.dependency-management version 1.1.6
    id org.graalvm.buildtools.native version 0.10.3
    id java
}

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(21) // 또는 25
    }
}

dependencies {
    implementation org.springframework.boot:spring-boot-starter-web
    implementation org.springframework.boot:spring-boot-starter-security
    implementation org.springframework.boot:spring-boot-starter-oauth2-client
    implementation org.springframework.boot:spring-boot-starter-data-jpa
    implementation org.springframework.boot:spring-boot-starter-actuator
    // Spring Boot BOM이 모든 버전 자동 관리
    testImplementation org.springframework.boot:spring-boot-starter-test
    testImplementation org.springframework.security:spring-security-test
}

Spring Boot 3.4는 기존 3.x 코드와의 호환성을 유지하면서도 최신 Java 기능 활용성을 극대화했습니다. 특히 spring.threads.virtual.enabled=true 한 줄로 가상 스레드를 활성화하고, RestClient 기반의 선언적 HTTP 클라이언트를 활용하면 기존 RestTemplate 기반 코드보다 훨씬 간결하고 유지보수하기 쉬운 코드를 작성할 수 있습니다.