Spring Boot 3.5 소개

Spring Boot 3.5(2026년 최신 안정 버전)는 Java 21+의 가상 스레드 지원 강화, 구조적 로깅, 개선된 SSL 설정 등 운영 환경에서 바로 활용할 수 있는 기능들을 담고 있습니다. Java 17 이상이 필요하며 Java 21 이상을 권장합니다.

1. Virtual Threads 통합 설정

Spring Boot 3.2부터 지원된 가상 스레드가 3.5에서 더욱 안정적으로 통합되었습니다.

# application.yml
spring:
  threads:
    virtual:
      enabled: true  # 이 한 줄로 Tomcat + @Async 모두 가상 스레드 활성화
// build.gradle
plugins {
    id "org.springframework.boot" version "3.5.5"
    id "io.spring.dependency-management" version "1.1.7"
    id "java"
}

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

dependencies {
    implementation "org.springframework.boot:spring-boot-starter-web"
    implementation "org.springframework.boot:spring-boot-starter-data-jpa"
    implementation "org.springframework.boot:spring-boot-starter-actuator"
    implementation "org.springframework.boot:spring-boot-starter-security"
}
// @Async도 자동으로 가상 스레드 사용
@Service
public class EmailService {

    @Async
    public CompletableFuture sendEmail(String to, String subject, String body) {
        // spring.threads.virtual.enabled=true 설정 시 가상 스레드에서 실행
        emailClient.send(to, subject, body); // 블로킹 I/O OK
        return CompletableFuture.completedFuture(null);
    }
}

2. Structured Logging (구조적 로깅)

Spring Boot 3.4에서 도입된 구조적 로깅이 3.5에서 더욱 강화되었습니다. JSON 형식 로그를 ELK Stack, Grafana Loki 등과 바로 연동할 수 있습니다.

# application.yml - Logstash JSON 형식 활성화
logging:
  structured:
    format:
      console: logstash  # 콘솔에 JSON 로그
      # file: ecs         # 파일에 Elastic Common Schema 형식
// 구조적 로그 출력 예시
{
  "@timestamp": "2026-04-13T10:30:00.123Z",
  "level": "INFO",
  "logger": "com.example.UserService",
  "message": "사용자 로그인 성공",
  "userId": "user123",
  "requestId": "req-abc-456",
  "duration_ms": 45
}
// 커스텀 필드 추가 - MDC 활용
@Component
public class RequestLoggingFilter implements Filter {

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        String requestId = UUID.randomUUID().toString();
        MDC.put("requestId", requestId);
        MDC.put("userId", getCurrentUserId());
        try {
            chain.doFilter(req, res);
        } finally {
            MDC.clear(); // 반드시 정리
        }
    }
}

@Service
@Slf4j
public class UserService {
    public User findUser(Long id) {
        log.info("사용자 조회 시작", StructuredArguments.kv("userId", id));
        User user = userRepository.findById(id).orElseThrow();
        log.info("사용자 조회 완료"); // requestId, userId가 자동으로 포함됨
        return user;
    }
}

3. WebClient 글로벌 설정 (Spring Boot 3.5 신기능)

Spring Boot 3.5에서는 properties 파일로 WebClient의 전역 설정이 가능해졌습니다.

# application.yml - WebClient 글로벌 설정
spring:
  webclient:
    connect-timeout: 5s
    read-timeout: 30s
    max-in-memory-size: 10MB
// RestClient 사용 예시 (WebClient의 동기 버전)
@Configuration
public class ApiConfig {

    @Bean
    public RestClient githubClient(RestClient.Builder builder) {
        return builder
            .baseUrl("https://api.github.com")
            .defaultHeader("Authorization", "Bearer " + token)
            .defaultHeader("Accept", "application/vnd.github+json")
            .build();
    }
}

@Service
public class GithubService {

    private final RestClient githubClient;

    public List getUserRepos(String username) {
        return githubClient.get()
            .uri("/users/{username}/repos", username)
            .retrieve()
            .body(new ParameterizedTypeReference>() {});
    }
}

4. @ConfigurationProperties 개선

// Record를 활용한 불변 설정 클래스
@ConfigurationProperties(prefix = "app.payment")
@Validated
public record PaymentProperties(
    @NotBlank String apiKey,
    @NotNull @Valid RetryConfig retry,
    @DurationUnit(ChronoUnit.SECONDS) Duration timeout
) {
    public record RetryConfig(
        @Min(1) @Max(10) int maxAttempts,
        @DurationUnit(ChronoUnit.MILLIS) Duration backoff
    ) {}
}
# application.yml
app:
  payment:
    api-key: ${PAYMENT_API_KEY}
    timeout: 30s
    retry:
      max-attempts: 3
      backoff: 500ms

5. Spring Security 6.x 최신 설정

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtConverter()))
            )
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .csrf(csrf -> csrf.disable()) // JWT 사용 시 CSRF 비활성화
            .cors(cors -> cors.configurationSource(corsConfigurationSource()));

        return http.build();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowedOriginPatterns(List.of("https://*.example.com"));
        config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        config.setAllowedHeaders(List.of("*"));
        config.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/api/**", config);
        return source;
    }
}

6. Actuator 보안 강화 (Spring Boot 3.5)

Spring Boot 3.5에서 heapdump 엔드포인트의 기본값이 access=NONE으로 변경되어 실수로 민감 정보가 노출되는 것을 방지합니다.

# application.yml - Actuator 설정
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus  # 필요한 것만 노출
  endpoint:
    health:
      show-details: when-authorized
    heapdump:
      access: none  # 3.5 기본값 - 명시적으로 허용하지 않으면 비활성화
  server:
    port: 8081  # 별도 포트로 분리 (운영 환경 권장)

마치며

Spring Boot 3.5는 운영 환경에서의 안정성과 관측 가능성(Observability)을 크게 강화했습니다. 가상 스레드 설정 한 줄로 Tomcat의 처리량을 대폭 높이고, 구조적 로깅으로 운영 모니터링을 체계화할 수 있습니다.