How to Configure Property Load Order with LoadType.FIRST vs LoadType.MERGE in OWNER
OWNER determines property source precedence through the @LoadPolicy annotation, which accepts either LoadType.FIRST to stop at the first readable source or LoadType.MERGE to combine all sources with the first-declared URI taking precedence for duplicate keys.
The OWNER configuration library (matteobaccan/owner) manages how multiple property files are consumed by your application. By default, the framework employs a first-match-wins strategy, but you can override this to layer configurations using the MERGE policy defined in org.aeonbits.owner.Config.LoadType.
Understanding the LoadType Enumeration
The load behavior is controlled by the LoadType enum defined in [owner/src/main/java/org/aeonbits/owner/Config.java](https://github.com/matteobaccan/owner/blob/master/owner/src/main/java/org/aeonbits/owner/Config.java) (lines 123-150). This enum provides two constants:
LoadType.FIRST– Loads the first successfully readable source from the@Sourceslist and stops immediately.LoadType.MERGE– Loads all reachable sources in reverse order, merging them into a singlePropertiesobject where earlier declarations override later ones.
During object construction, PropertiesManager extracts this policy in its constructor ([owner/src/main/java/org/aeonbits/owner/PropertiesManager.java](https://github.com/matteobaccan/owner/blob/master/owner/src/main/java/org/aeonbits/owner/PropertiesManager.java), lines 97-107). If no @LoadPolicy annotation is present, the system defaults to FIRST via the assignment loadType = (loadPolicy != null) ? loadPolicy.value() : FIRST;.
Using LoadType.FIRST for Fallback Hierarchies
The FIRST strategy processes the @Sources list sequentially from top to bottom. It attempts to load each URI until one succeeds, then returns immediately without processing remaining sources. This creates a clean fallback chain where missing or unreadable files are silently skipped via the ignore() mechanism.
Use this approach when you need a clear priority order, such as checking for a user-specific override before falling back to bundled defaults.
@Sources({
"file:${user.home}/app.conf", // Try user-specific file first
"classpath:default.properties" // Fallback to classpath default
})
public interface AppConfig extends Config {
String host();
int port();
}
In this example, OWNER attempts to load the file from the user's home directory. Only if that file is missing or unreadable does it proceed to load default.properties from the classpath. The effective configuration contains properties from exactly one source.
Using LoadType.MERGE for Configuration Layering
The MERGE strategy inverts the @Sources list using reverse(uris) and loads every reachable file into the same Properties object. Because the list is processed in reverse, the first entry in your annotation has the highest precedence. When the same key exists in multiple files, the value from the earliest source in the list prevails, as subsequent loads cannot overwrite existing entries.
Use this when you need to combine base configurations with environment-specific overlays.
@Sources({
"classpath:base.properties", // Highest priority (declared first)
"classpath:environment.properties", // Overrides base only for new keys
"file:/etc/app/override.properties" // Lowest priority
})
@LoadPolicy(LoadType.MERGE)
public interface LayeredConfig extends Config {
String databaseUrl();
String logLevel();
}
Here, all three files are loaded. If databaseUrl appears in both base.properties and environment.properties, the value from base.properties wins because it appears first in the annotation list. This behavior is verified in the unit test [MergeLoadStrategyTest.java](https://github.com/matteobaccan/owner/blob/master/owner/src/test/java/org/aeonbits/owner/loadstrategies/MergeLoadStrategyTest.java).
Implementation Behavior for Missing Resources
Both strategies handle unreachable URIs identically: they invoke the ignore() method and continue processing. This means your application will not crash if a fallback file is missing, regardless of whether you use FIRST or MERGE. The difference lies solely in how many sources are ultimately incorporated into the final configuration object.
Summary
LoadType.FIRSTprocesses@Sourcesin declared order and stops at the first successful load, creating a single-source configuration ideal for fallback chains.LoadType.MERGEreverses the source list, loads all reachable files, and merges them so that the first-declared source takes precedence for overlapping keys.- The default policy is
FIRSTwhen no@LoadPolicyannotation is specified, as implemented inPropertiesManager.java. - Both policies silently skip missing or unreadable URIs, allowing optional configuration files in your hierarchy.
Frequently Asked Questions
What is the default load policy in OWNER?
If you do not annotate your configuration interface with @LoadPolicy, OWNER defaults to LoadType.FIRST. This is explicitly set in the PropertiesManager constructor at line 107, where loadType is assigned FIRST when no annotation is detected.
How does LoadType.MERGE handle duplicate property keys?
When using MERGE, OWNER iterates through the @Sources list in reverse order. Because the Properties object retains the first value written for any key, the source that appears first in your annotation (processed last in the reversed iteration) wins for duplicate keys. Later loads cannot overwrite existing entries.
Can I use different LoadType strategies for different configuration interfaces?
Yes. Each configuration interface is independent, and ConfigFactory.create() instantiates a separate PropertiesManager for each one. You can annotate DatabaseConfig with @LoadPolicy(MERGE) while leaving SecurityConfig to use the default FIRST behavior.
Does OWNER throw an exception if a source file is missing?
No. Both FIRST and MERGE strategies silently ignore unreachable URIs through the ignore() mechanism in the loading logic. The framework only throws an exception if a successfully loaded file contains invalid data or if a required property key is missing from the final merged result.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →