Mastering @PropertySource with YAML Files in Spring Boot: Advanced Configuration
Learn how to use @PropertySource with YAML files in Spring Boot 3.2.
20+ years shipping production Java in banking & fintech. Everything here is grounded in real deployments.
- ✓Java 17+ and Spring Boot 3.2+
- ✓Basic understanding of Spring Boot configuration (application.yml)
- ✓Familiarity with Maven or Gradle build tools
• @PropertySource natively supports .properties but not YAML files in Spring Boot 3.2. • Use a custom PropertySourceFactory (e.g., YamlPropertySourceFactory) to load YAML files. • Environment variables override YAML values; understand precedence to avoid production surprises. • For SaaS billing, externalize YAML config per tenant using profiles and @PropertySource. • Always validate YAML syntax with YAMLint before deployment to prevent silent failures.
Think of @PropertySource as a key-value map for your app. YAML is like a nested folder structure (like a file cabinet with drawers and folders). Spring Boot's default @PropertySource only reads flat lists (like a single sheet of paper), not nested folders. To read YAML, you need a special adapter (YamlPropertySourceFactory) that understands how to flatten the folders into keys like 'database.host'. Without it, Spring ignores your YAML file entirely – like trying to open a cabinet with the wrong key.
In real-world Spring Boot applications, configuration is the backbone of reliability. I've seen production outages in payment-processing systems caused by misconfigured YAML files that @PropertySource silently ignored. The default @PropertySource annotation in Spring Boot 3.2 supports only .properties files – a fact that trips up even senior developers. When you try to load a YAML file with @PropertySource("classpath:config.yml"), Spring throws a silent warning and loads nothing. This article reveals how to properly load YAML with @PropertySource using a custom PropertySourceFactory, a pattern I've used in SaaS billing platforms handling millions of transactions daily. We'll cover the YamlPropertySourceFactory, precedence rules, profile-specific YAML, and production debugging. You'll learn why relying solely on application.yml can be a trap for multi-tenant systems, and how to externalize per-tenant config without classpath pollution. By the end, you'll configure Spring Boot like a battle-hardened architect.
Why @PropertySource Doesn't Support YAML Out-of-the-Box
In Spring Boot 3.2, the @PropertySource annotation is designed around Java's Properties class, which is a flat key-value structure. YAML is hierarchical and requires a parser to flatten keys like 'database.host' from nested structures. Spring Boot's default PropertySourceFactory implementations (DefaultPropertySourceFactory and ResourcePropertySource) only handle .properties files. When you point @PropertySource at a YAML file, Spring Boot logs a debug message like 'Could not parse YAML file: class path resource [config.yml]' and returns an empty source. This is a silent failure – your app starts, but configuration is missing. I've seen this cause null pointer exceptions in payment-processing services because a database URL was never loaded. The fix is to provide a custom factory that uses SnakeYAML (bundled with Spring Boot) to parse YAML and convert it to a Map-based PropertySource. Spring Boot itself uses YamlPropertySourceLoader internally for application.yml, but it's not wired to @PropertySource. That's the gap you need to bridge.
What the Official Docs Won't Tell You
The official Spring Boot documentation (v3.2) states that @PropertySource is for .properties files, but it doesn't explicitly warn that YAML fails silently. In production, I've seen teams waste hours debugging missing configs. Here's what the docs omit: First, the YamlPropertySourceFactory must return a PropertiesPropertySource, not a MapPropertySource, because Spring's Environment expects flat keys. Second, YAML lists (e.g., 'servers: [a, b]') become comma-separated strings, not arrays – you must parse them manually if you need a List. Third, profile-specific YAML documents (using '---' and 'spring.profiles') are not supported by this factory; it only loads the first document. For multi-profile YAML, use separate files per profile. Fourth, the factory does not support placeholder resolution (${...}) inside YAML values – that's handled by Spring later. Finally, if your YAML has duplicate keys, the last one wins, which can cause subtle bugs. In a payment-processing app, a duplicate 'api.timeout' key caused intermittent timeouts. The fix was to validate YAML with a linter in CI.
Setting Up YamlPropertySourceFactory Step by Step
To use @PropertySource with YAML, you need three things: a custom PropertySourceFactory, the YAML file, and the annotation. Start by adding SnakeYAML dependency (it's included by default in Spring Boot, but if you're using a non-Boot project, add 'org.yaml:snakeyaml:2.2'). Then create the factory class as shown earlier. Place your YAML file in 'src/main/resources' – for a SaaS billing app, we use 'tenant-config.yml' per tenant. Annotate a configuration class with @PropertySource, specifying the factory. Important: the file path must be resolvable at classpath. For external files, use 'file:/path/to/config.yml'. After startup, verify with 'env.getProperty("key")'. I recommend adding a @PostConstruct method that logs all loaded keys for debugging. In production, we also expose a custom actuator endpoint that lists all PropertySource names. This saved us when a config file was accidentally excluded from the JAR. Remember that @PropertySource is processed before @Value injection, so any @Value in the same class works fine.
Property Precedence: Where Does @PropertySource Fit?
Spring Boot's property resolution order is critical. In Spring Boot 3.2, the precedence (highest to lowest) is: command-line arguments, JNDI attributes, System.getProperties(), OS environment variables, RandomValuePropertySource, application-{profile}.yml (outside JAR), application-{profile}.yml (inside JAR), application.yml (outside), application.yml (inside), @PropertySource on @Configuration classes, and finally default properties. @PropertySource files are loaded at step 9, meaning they override application.yml but are overridden by environment variables. This is a common source of bugs: if you set 'DATABASE_URL' as an environment variable, it overrides your YAML value even if you use @PropertySource. In a payment-processing system, a developer set 'payment.gateway.timeout' in a YAML file loaded via @PropertySource, but an ops team member had set an environment variable 'PAYMENT_GATEWAY_TIMEOUT' (Spring Boot converts dots to underscores). The YAML value was ignored, causing timeouts. The fix was to use a unique prefix for @PropertySource keys that doesn't conflict with env vars. Also note that @PropertySource files are loaded in the order they appear if you use multiple annotations. Always test with a simple @Value to confirm which source wins.
Environment.getPropertySources(). This shows exactly which file won.Using YAML Lists and Complex Structures with @PropertySource
YAML supports lists and maps, but @PropertySource with YamlPropertySourceFactory flattens them into dot-separated keys. For example, a list 'servers: [a, b]' becomes 'servers[0]=a' and 'servers[1]=b'. To inject a List, use @Value("${servers}") which returns a comma-separated string – you must split it manually or use a custom converter. Better: use @ConfigurationProperties with a dedicated class. In a real-time analytics system, we had a YAML config for Kafka brokers: 'kafka.brokers: [broker1:9092, broker2:9092]'. Using @ConfigurationProperties with a List<String> field works seamlessly. But @PropertySource doesn't support @ConfigurationProperties binding directly – you need to load the YAML into the Environment first, then use @ConfigurationProperties on a separate bean. The workaround is to create a @Bean that reads the flattened keys and populates a POJO. For maps, YAML 'database: {host: localhost, port: 5432}' becomes 'database.host' and 'database.port'. This works fine with @Value. For nested maps, keys like 'database.credentials.user' are created. In SaaS billing, we use this pattern for per-tenant feature flags: 'features: {enable_new_payment: true}'.
Profile-Specific YAML with @PropertySource
Spring Boot's application-{profile}.yml is the idiomatic way for profile-specific config, but sometimes you need to load a different YAML file per profile via @PropertySource. For example, in a SaaS billing system, we have 'payment-config-dev.yml' and 'payment-config-prod.yml'. You can use Spring's @Profile annotation on the configuration class: @Profile("dev") @PropertySource("classpath:payment-config-dev.yml"). But this loads the file only when the 'dev' profile is active. If you need to load multiple files conditionally, use a @ConditionalOnProperty or @ConditionalOnExpression. A more flexible approach is to use a placeholder in the file path: @PropertySource("classpath:payment-config-${spring.profiles.active}.yml"). However, this fails if no profile is set (spring.profiles.active is empty). Always provide a default: @PropertySource("classpath:payment-config-${spring.profiles.active:default}.yml"). I recommend this pattern for multi-tenant systems where each tenant has a profile. In production, we also load a common base YAML file and then a tenant-specific override. The factory handles the flatting, so keys from both files merge correctly. Remember that @PropertySource files are processed in order, so the last one wins for duplicate keys.
Testing @PropertySource with YAML Files
Testing configuration loading is often overlooked. In Spring Boot 3.2, you can test @PropertySource with @SpringBootTest and @TestPropertySource, but the latter only supports .properties. For YAML, load the file manually in your test. I recommend a dedicated test that creates an ApplicationContext with your configuration class and asserts that properties are loaded. Use SpringExtension (JUnit 5) and @ContextConfiguration. For example, load a test YAML file 'test-config.yml' and verify keys. Also test the factory itself: create an instance of YamlPropertySourceFactory, call createPropertySource with a test resource, and assert the resulting PropertySource contains expected keys. This catches parsing errors early. In a payment-processing system, we had a YAML file with a tab character instead of spaces, causing a parsing exception. The factory test caught it. For integration tests, use @SpringBootTest with properties 'spring.config.location=classpath:/test-config.yml' to override the default. But be careful: this replaces all default config, so you may need to include the original application.yml as well. A better approach: use @TestPropertySource with a .properties file for test-specific overrides, and keep YAML for main config.
Production Debugging and Monitoring
When @PropertySource with YAML fails in production, symptoms are subtle: services start but behave incorrectly. In a real-time analytics system, a missing YAML file caused all metrics to be sent to a wrong Kafka topic. The debugging process: check /actuator/env to see if the property source is listed. If not, the file wasn't loaded. Enable debug logging for 'org.springframework.core.env' to see which files are processed. Use 'env.containsProperty("key")' in a custom endpoint. Also check file paths: 'classpath:' resolves to the root of the classpath. If your YAML is in a subdirectory, e.g., 'config/', use @PropertySource("classpath:config/file.yml"). For external files, use 'file:/etc/app/config.yml' and ensure the path is absolute. Another common issue: the YAML file is present but has syntax errors. Spring Boot's YamlPropertySourceFactory throws an exception if parsing fails, which crashes the context. In production, we wrapped the factory with a try-catch that logs the error and provides a fallback. Finally, monitor configuration changes: if you reload YAML at runtime (e.g., using Spring Cloud Config), ensure the new values propagate. Use @RefreshScope on beans that depend on @Value. In a SaaS billing system, we had a custom HealthIndicator that checks if critical config keys are present, alerting if missing.
Silent YAML Failure in Payment Gateway
- Never assume annotation behavior – read the docs for your Spring Boot version.
- Always validate that your configuration keys are actually loaded using /actuator/env.
- Use YAMLint in CI/CD pipelines to catch syntax errors early.
curl localhost:8080/actuator/env | grep 'propertySources'Check logs for 'DEBUG o.s.c.e.PropertySourcesPropertyResolver'| File | Command / Code | Purpose |
|---|---|---|
| YamlPropertySourceFactory.java | public class YamlPropertySourceFactory implements PropertySourceFactory { | Why @PropertySource Doesn't Support YAML Out-of-the-Box |
| UsageExample.java | @Configuration | What the Official Docs Won't Tell You |
| TenantConfig.java | @Configuration | Setting Up YamlPropertySourceFactory Step by Step |
| PrecedenceTest.java | @SpringBootApplication | Property Precedence |
| ListConfig.java | @Configuration | Using YAML Lists and Complex Structures with @PropertySource |
| ProfileSpecificConfig.java | @Configuration | Profile-Specific YAML with @PropertySource |
| YamlPropertySourceFactoryTest.java | public class YamlPropertySourceFactoryTest { | Testing @PropertySource with YAML Files |
| ConfigHealthIndicator.java | @Component | Production Debugging and Monitoring |
Key takeaways
Interview Questions on This Topic
Explain why @PropertySource doesn't support YAML files by default in Spring Boot.
Frequently Asked Questions
20+ years shipping production Java in banking & fintech. Everything here is grounded in real deployments.
That's Spring Boot. Mark it forged?
5 min read · try the examples if you haven't