@ConfigurationProperties란?
Spring Boot의 @ConfigurationProperties는 application.yml 또는 환경 변수의 설정값을 타입 안전한 Java 객체로 바인딩하는 메커니즘이다. @Value와 달리 계층 구조, 컬렉션, 중첩 객체를 자연스럽게 매핑하며 검증(Validation)까지 통합할 수 있다.
기본 사용법: Record 바인딩
Spring Boot 3.x부터 Java Record를 @ConfigurationProperties에 직접 사용할 수 있다. 불변(immutable) 설정 객체를 생성자 바인딩으로 만들 수 있어 가장 권장되는 패턴이다.
// application.yml
// app:
// name: my-service
// api:
// base-url: https://api.example.com
// timeout: 5s
// retry-count: 3
@ConfigurationProperties(prefix = "app")
public record AppProperties(
String name,
ApiProperties api
) {
public record ApiProperties(
String baseUrl,
Duration timeout,
@DefaultValue("3") int retryCount
) {}
}
@DefaultValue로 기본값을 지정하고, Duration 타입은 5s, 2m, 1h 같은 문자열을 자동 파싱한다.
활성화 방법: @EnableConfigurationProperties vs @ConfigurationPropertiesScan
// 방법 1: 명시적 등록
@Configuration
@EnableConfigurationProperties(AppProperties.class)
public class AppConfig {}
// 방법 2: 패키지 스캔 (Spring Boot 2.2+)
@SpringBootApplication
@ConfigurationPropertiesScan("com.example.config")
public class Application {}
프로젝트 규모가 커지면 @ConfigurationPropertiesScan이 편리하다. 단, 스캔 범위를 명시해 불필요한 클래스가 등록되지 않도록 한다.
Relaxed Binding: 유연한 속성 매핑
Spring Boot는 Relaxed Binding이라는 규칙으로 다양한 형식의 속성 이름을 동일 필드에 매핑한다.
| 소스 형식 | 예시 | 매핑 대상 |
|---|---|---|
| kebab-case (yml 권장) | api.base-url |
baseUrl |
| camelCase | api.baseUrl |
baseUrl |
| UPPER_SNAKE (환경 변수) | APP_API_BASE_URL |
baseUrl |
| underscore | api.base_url |
baseUrl |
환경 변수로 설정을 오버라이드할 때 이 규칙을 알아야 한다. app.api.base-url은 환경 변수 APP_API_BASE_URL로 덮어쓸 수 있다. Spring Profiles 환경 분리와 함께 사용하면 환경별 설정 관리가 깔끔해진다.
컬렉션과 Map 바인딩
// application.yml
// storage:
// endpoints:
// - https://s3-1.example.com
// - https://s3-2.example.com
// headers:
// X-Api-Key: secret123
// X-Region: ap-northeast-2
@ConfigurationProperties(prefix = "storage")
public record StorageProperties(
List<String> endpoints,
Map<String, String> headers
) {}
List는 인덱스 기반(endpoints[0])으로, Map은 키-값 쌍으로 바인딩된다. 환경 변수에서 List를 설정할 때는 STORAGE_ENDPOINTS_0=https://... 형식을 사용한다.
Validation: @Validated + JSR-303
설정값 검증은 애플리케이션 시작 시점에 수행되므로, 잘못된 설정이 런타임까지 도달하지 않는다.
@Validated
@ConfigurationProperties(prefix = "app.api")
public record ApiProperties(
@NotBlank String baseUrl,
@DurationMin(seconds = 1) @DurationMax(minutes = 1) Duration timeout,
@Min(0) @Max(10) int retryCount
) {}
// 잘못된 설정 시 시작 실패:
// Binding validation errors on app.api
// - Field 'baseUrl': must not be blank
// - Field 'timeout': must be at least 1s
@Validated를 클래스 레벨에 선언해야 JSR-303 어노테이션이 동작한다. 중첩 객체에는 @Valid를 추가한다.
@Value와의 차이점
| 비교 항목 | @ConfigurationProperties | @Value |
|---|---|---|
| 타입 안전성 | ✅ 컴파일 타임 | ❌ 런타임 SpEL |
| 중첩 구조 | ✅ 자연스러운 계층 | ❌ 플랫 키만 |
| Relaxed Binding | ✅ 지원 | ❌ 미지원 |
| 검증 | ✅ JSR-303 | ❌ 수동 처리 |
| 테스트 | ✅ Mock 객체 주입 용이 | ⚠️ 필드 주입 Mocking 어려움 |
| 적합 상황 | 설정 그룹 (3개+) | 단일 값 1~2개 |
Custom Starter에서의 활용
사내 공통 라이브러리를 Spring Boot Custom Starter로 만들 때 @ConfigurationProperties는 핵심이다.
// my-company-starter의 자동 설정
@AutoConfiguration
@EnableConfigurationProperties(CompanyApiProperties.class)
@ConditionalOnProperty(prefix = "company.api", name = "enabled", havingValue = "true")
public class CompanyApiAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public CompanyApiClient companyApiClient(CompanyApiProperties props) {
return new CompanyApiClient(props.baseUrl(), props.timeout());
}
}
// META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
// com.company.starter.CompanyApiAutoConfiguration
사용하는 쪽에서는 application.yml에 company.api.base-url만 설정하면 된다. IDE 자동 완성을 위해 spring-boot-configuration-processor를 추가하면 META-INF/spring-configuration-metadata.json이 생성된다.
IDE 자동 완성: Configuration Processor
// build.gradle.kts
dependencies {
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
}
// 빌드 후 생성되는 메타데이터 예시
// target/classes/META-INF/spring-configuration-metadata.json
{
"groups": [{ "name": "app.api", "type": "com.example.ApiProperties" }],
"properties": [
{ "name": "app.api.base-url", "type": "java.lang.String" },
{ "name": "app.api.timeout", "type": "java.time.Duration", "defaultValue": "5s" }
]
}
이 메타데이터 파일 덕분에 IntelliJ/VSCode에서 application.yml 작성 시 속성 자동 완성, 타입 힌트, 설명이 표시된다.
프로파일별 오버라이드 패턴
// application.yml (기본값)
app:
api:
base-url: https://api.example.com
timeout: 5s
// application-prod.yml (프로덕션 오버라이드)
app:
api:
timeout: 2s # 프로덕션은 타임아웃을 짧게
// 환경 변수 최종 오버라이드 (K8s ConfigMap → env)
// APP_API_BASE_URL=https://api-internal.prod.svc.cluster.local
우선순위: 환경 변수 > application-{profile}.yml > application.yml. K8s 배포 시 ConfigMap/Secret을 환경 변수로 주입하면 코드 변경 없이 설정을 바꿀 수 있다.
테스트에서의 활용
@SpringBootTest
@TestPropertySource(properties = {
"app.api.base-url=https://mock.test",
"app.api.timeout=1s",
"app.api.retry-count=0"
})
class ApiServiceTest {
@Autowired
private AppProperties properties;
@Test
void shouldBindTestProperties() {
assertThat(properties.api().baseUrl()).isEqualTo("https://mock.test");
assertThat(properties.api().timeout()).isEqualTo(Duration.ofSeconds(1));
}
}
// Record이므로 단위 테스트에서 직접 생성도 가능
@Test
void unitTest() {
var props = new AppProperties.ApiProperties("https://mock", Duration.ofSeconds(1), 0);
var service = new ApiService(props);
// ...
}
주의사항과 베스트 프랙티스
- setter 바인딩 vs 생성자 바인딩 — Record나
@ConstructorBinding을 사용한 불변 객체를 권장한다. JavaBean 방식(setter)은 설정값이 런타임에 변경될 수 있어 위험하다. - prefix 네이밍 — 회사명이나 모듈명을 prefix로 사용하라 (
mycompany.email). Spring 내부 키(spring.*)와 충돌을 방지한다. - 민감 정보 — 비밀번호, API 키는 yml에 평문으로 두지 말고 환경 변수나 Vault로 주입하라.
@ConfigurationProperties는 소스에 상관없이 동일하게 바인딩한다. - @NestedConfigurationProperty — 외부 라이브러리 타입을 중첩 속성으로 사용할 때 이 어노테이션을 붙여야 Configuration Processor가 메타데이터를 생성한다.
- 3rd-party 설정 바인딩 —
@Bean메서드에@ConfigurationProperties를 붙이면 외부 클래스에도 바인딩할 수 있다.
정리
@ConfigurationProperties는 단순히 설정값을 읽는 도구가 아니라, 설정의 타입 안전성, 검증, 문서화, 테스트 용이성을 한번에 해결하는 Spring Boot의 핵심 기능이다. Record 바인딩 + JSR-303 검증 + Configuration Processor 조합이면 설정 관련 버그를 시작 시점에 잡을 수 있다. @Value 3개 이상이 보이면 @ConfigurationProperties로 리팩터링을 고려하라.