도입
Spring Boot를 쓰다 보면 같은 코드베이스 안에서도 어떤 기능은 켜고, 어떤 기능은 끄고, 어떤 자동 설정은 특정 환경에서만 적용하고 싶을 때가 많습니다.
이때 가장 자주 쓰이는 조건 중 하나가 @ConditionalOnProperty 입니다. 이름 그대로 Spring Environment 안에 들어 있는 프로퍼티 값을 보고 설정을 활성화할지 결정합니다.
그래서 이 애노테이션을 이해한다는 것은 단순히 문법을 외우는 일이 아니라, Spring Boot가 설정값을 기준으로 자동 설정과 빈 등록을 어떻게 분기하는지 이해하는 일과 거의 같습니다.
필요성
실무에서는 모든 빈을 항상 등록해 두는 방식보다, 특정 프로퍼티가 있을 때만 기능을 켜는 방식이 훨씬 자주 쓰입니다. 예를 들어 캐시, 외부 클라이언트, 커스텀 로깅, 특정 스케줄러를 필요할 때만 활성화하고 싶을 수 있습니다.
특히 라이브러리나 사내 공통 starter를 만들 때는 기본값을 유지하면서도 사용자가 설정으로 쉽게 opt-in 또는 opt-out 할 수 있어야 합니다. @ConditionalOnProperty는 이런 요구를 가장 단순한 형태로 처리하게 해 줍니다.
즉 이 애노테이션은 단순한 if문 대체제가 아니라, Spring Boot 스타일의 설정 기반 분기를 구성하는 핵심 도구에 가깝습니다.
- starter나 auto-configuration에서 기능을 설정값으로 켜고 끌 때
- 특정 외부 시스템 연동을 opt-in 방식으로 활성화할 때
- 개발/운영 환경에서 빈 등록 전략을 분리하고 싶을 때
- 기본 동작은 유지하되 사용자가 프로퍼티로 세부 기능을 제어하게 하고 싶을 때
정의
@ConditionalOnProperty 는 @Configuration 클래스에도 붙일 수 있고, 개별 @Bean 메서드에도 붙일 수 있습니다. 즉 설정 단위 전체를 끌 수도 있고, 특정 빈 하나만 조건부로 등록할 수도 있습니다.
기본 동작은 생각보다 단순합니다. 프로퍼티가 Spring Environment에 존재하고 그 값이 문자열 기준으로 false 가 아니면 조건이 매치됩니다.
여기에 havingValue 로 원하는 값을 더 엄격하게 지정할 수 있고, 프로퍼티가 아예 없을 때도 조건을 맞춘 것으로 볼지 matchIfMissing 로 정할 수 있습니다.
"@ConditionalOnProperty 는 설정 파일을 읽는 애노테이션이 아니라
Environment 에 최종적으로 들어온 프로퍼티 값을 기준으로 빈 등록 여부를 결정하는 조건이다."
핵심 원리
Spring Boot 공식 문서 기준으로 프로퍼티 값은 @Value, @ConfigurationProperties, 그리고 Spring Environment를 통해 접근됩니다. @ConditionalOnProperty 역시 결국 이 Environment를 기준으로 검사합니다.
즉 application.properties, YAML, OS 환경 변수, Java System properties, 커맨드라인 인자, 테스트용 프로퍼티처럼 Environment에 들어오는 값이라면 모두 조건에 영향을 줄 수 있습니다.
또 Spring Boot는 property source 우선순위를 가지고 있으므로, 같은 키라도 나중 우선순위의 값이 앞의 값을 덮어쓸 수 있습니다. 그래서 @ConditionalOnProperty가 왜 예상과 다르게 동작하는지 볼 때는 파일 하나가 아니라 최종 Environment 값을 봐야 합니다.
결국 이 애노테이션은 “설정 파일을 읽는다”보다 “현재 Environment 의 최종 상태를 보고 분기한다”로 이해하는 편이 더 정확합니다.
Condition Evaluation Flow
1) Spring Boot가 여러 PropertySource를 Environment에 적재
2) 우선순위에 따라 최종 프로퍼티 값 결정
3) @ConditionalOnProperty 가 prefix + name 으로 키를 구성
4) havingValue / matchIfMissing 규칙으로 조건 평가
5) 설정 클래스 또는 @Bean 등록 여부 결정
주요 속성
| 속성 | 역할 | 실무 해석 |
|---|---|---|
| prefix | 프로퍼티 앞부분 경로 | 자동으로 점(.)이 붙으므로 app.feature처럼 적으면 됨 |
| name | 검사할 프로퍼티 이름 | 대시 표기(my-long-property) 권장, 여러 개면 모두 통과해야 함 |
| value | name의 별칭 |
짧게 쓰고 싶을 때 사용 가능하지만 실무에서는 name이 더 읽기 쉬운 편 |
| havingValue | 기대한 문자열 값 | 지정하지 않으면 기본 규칙은 “false가 아니면 매치” |
| matchIfMissing | 프로퍼티가 없을 때도 매치할지 여부 | 기본값은 false, 즉 프로퍼티가 빠져 있으면 기본적으로 매치되지 않음 |
특히 prefix 는 마지막 점을 직접 붙이지 않아도 자동으로 보정됩니다. 또한 name 은 lower-case dashed notation을 권장하고, 여러 이름을 지정하면 하나라도 실패할 경우 전체 조건이 매치되지 않습니다.
기본 동작
| 프로퍼티 값 | havingValue="" |
havingValue="true" |
havingValue="false" |
havingValue="foo" |
|---|---|---|---|---|
true |
match | match | no match | no match |
false |
no match | no match | match | no match |
foo |
match | no match | no match | match |
| 프로퍼티 없음 | matchIfMissing 참조 |
matchIfMissing 참조 |
matchIfMissing 참조 |
matchIfMissing 참조 |
실무에서 가장 많이 헷갈리는 부분이 바로 이것입니다. havingValue 를 비워 두면 true 만 허용하는 것이 아니라, 문자열 기준으로 false 만 아니면 매치됩니다. 따라서 명확한 boolean 토글이라면 의도를 더 분명하게 적는 편이 좋습니다.
기본 구현
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "app.feature", name = "enabled", havingValue = "true")
public class FeatureConfiguration {
@Bean
public FeatureService featureService() {
return new FeatureService();
}
}
@Configuration(proxyBeanMethods = false)
public class ClientConfiguration {
@Bean
@ConditionalOnProperty(prefix = "app.client", name = "mock", havingValue = "true")
public Client mockClient() {
return new MockClient();
}
@Bean
@ConditionalOnProperty(prefix = "app.client", name = "mock", havingValue = "false", matchIfMissing = true)
public Client realClient() {
return new RealClient();
}
}
패턴 1. 기능 토글용 enabled 플래그
가장 흔한 패턴은 xxx.enabled 형태의 boolean 비슷한 플래그를 두는 것입니다. 사용자는 설정만 바꿔 기능을 켜고 끌 수 있고, 코드는 if문 없이도 빈 등록 자체를 조건부로 만들 수 있습니다.
이때 중요한 것은 기본값을 어디에 둘 것인지입니다. 프로퍼티가 없으면 기본적으로 매치되지 않으므로, 기능을 기본 on 으로 둘지 기본 off 로 둘지 matchIfMissing 까지 포함해 함께 설계해야 합니다.
@ConditionalOnProperty(
prefix = "app.metrics",
name = "enabled",
havingValue = "true",
matchIfMissing = true
)
패턴 2. 구현체 선택 분기
이 애노테이션은 단순한 on/off 뿐 아니라 “어떤 구현을 쓸 것인가”를 나누는 데도 자주 사용됩니다. 예를 들어 개발 환경에서는 mock, 운영에서는 real, 특정 테스트에서는 noop 구현을 쓰도록 분기할 수 있습니다.
이 경우 havingValue 를 사용해 문자열 값으로 구현체를 나누면 명시성이 좋아집니다. 단, 여러 구현이 동시에 매치되지 않도록 값 설계를 분명히 해야 합니다.
@Bean
@ConditionalOnProperty(prefix = "app.storage", name = "type", havingValue = "s3")
StorageClient s3StorageClient() {
return new S3StorageClient();
}
@Bean
@ConditionalOnProperty(prefix = "app.storage", name = "type", havingValue = "local", matchIfMissing = true)
StorageClient localStorageClient() {
return new LocalStorageClient();
}
패턴 3. boolean 전용이면 ConditionalOnBooleanProperty 검토
Spring Boot 현재 문서에는 @ConditionalOnBooleanProperty 가 따로 존재합니다. 이 애노테이션은 boolean 값 전용으로 조건을 검사하며, 기본 동작도 “프로퍼티가 존재하고 true 일 때 매치”라서 의미가 더 직접적입니다.
즉 값이 문자열로 다양하게 갈 수 있는 경우에는 @ConditionalOnProperty 가 자연스럽고, 정말 boolean 토글만 다룬다면 전용 애노테이션이 더 읽기 쉬운 선택이 될 수 있습니다.
@Bean
@ConditionalOnBooleanProperty(prefix = "app.feature", name = "enabled", matchIfMissing = true)
FeatureService featureService() {
return new FeatureService();
}
@ConditionalOnProperty 를 boolean 용도로도 충분히 쓸 수 있지만, 기본 규칙이 “false 가 아니면 매치”라는 점 때문에 의도가 흐려질 수 있습니다. 순수한 true/false 토글이면 전용 애노테이션이 더 명확한 경우가 많습니다.한계와 주의점
Javadoc이 명시적으로 경고하는 부분이 하나 있습니다. 이 애노테이션은 컬렉션 프로퍼티 매칭에 신뢰성 있게 사용할 수 없습니다.
예를 들어 spring.example.values 가 있을 때는 매치되더라도, 실제 환경에 spring.example.values[0] 만 잡혀 있는 경우에는 기대와 다르게 동작할 수 있습니다. 이런 경우는 커스텀 조건을 쓰는 편이 더 안전합니다.
또한 이 애노테이션은 문자열 값 기반 비교라는 점도 잊기 쉽습니다. boolean 처럼 보여도 결국 문자열 비교 규칙 위에서 돌아간다는 점을 항상 염두에 둬야 합니다.
- 프로퍼티 파일이 아니라 Environment 최종값을 기준으로 평가된다는 점
- 기본 규칙이 “true 일 때”가 아니라 “false 가 아니면”이라는 점
- 컬렉션 프로퍼티에는 신뢰성 있게 쓰기 어렵다는 점
- 여러
name을 주면 OR 이 아니라 AND 로 동작한다는 점
자주 하는 실수
havingValue를 비워 두면 true 만 매치된다고 착각함name여러 개를 주고 OR 처럼 동작할 것이라 생각함- YAML 파일 값만 보고 실제 Environment 최종값은 확인하지 않음
matchIfMissing를 빼먹어 기본 on/off 의도가 뒤집힘- 컬렉션 프로퍼티 존재 여부를 이 애노테이션 하나로 처리하려고 함
- boolean 토글인데도 문자열 규칙을 의식하지 않고 막연히 사용함
실무 루틴
- 프로퍼티 키는
app.feature.enabled처럼 의도가 보이게 설계한다. - 기본 on/off 정책은
matchIfMissing로 명시한다. - 값 비교가 명확하지 않다면
havingValue를 생략하지 않는다. - 여러 이름을 동시에 검사할 때는 AND 조건임을 전제로 설계한다.
- 컬렉션이나 복잡한 구조는 커스텀
Condition으로 분리한다. - auto-configuration 테스트는
ApplicationContextRunner로 케이스별 검증을 남긴다.
디버깅
--debug 로 실행해 조건 평가 보고서를 먼저 본다./actuator/conditions 로 어떤 조건이 왜 매치되었는지 확인한다.prefix.name 형태로 조합된 최종 키와 일치하는지 본다.ApplicationContextRunner 로 프로퍼티 케이스별 테스트를 만든다.자주 쓰는 점검 포인트
--debug
/actuator/conditions
prefix + name 조합 확인
havingValue / matchIfMissing 재검토
Environment 우선순위 확인
ApplicationContextRunner 로 케이스 테스트
요약
- ✅
@ConditionalOnProperty는 SpringEnvironment프로퍼티를 기준으로 조건을 평가한다. - ✅ 기본 규칙은 프로퍼티가 존재하고 값이
false가 아니면 매치다. - ✅
havingValue와matchIfMissing로 조건을 더 정교하게 만들 수 있다. - ✅
prefix는 자동으로 점을 붙이고,name은 dashed notation을 권장한다. - ✅ 여러
name을 지정하면 모두 통과해야 한다. - ✅ 클래스 레벨과
@Bean메서드 레벨 모두에 적용할 수 있다. - ✅ 컬렉션 프로퍼티 매칭에는 신뢰성 있게 쓰기 어렵다.
- ✅ boolean 토글이라면
@ConditionalOnBooleanProperty도 고려할 수 있다.