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 @Sources list and stops immediately.
  • LoadType.MERGE – Loads all reachable sources in reverse order, merging them into a single Properties object 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.FIRST processes @Sources in declared order and stops at the first successful load, creating a single-source configuration ideal for fallback chains.
  • LoadType.MERGE reverses 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 FIRST when no @LoadPolicy annotation is specified, as implemented in PropertiesManager.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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →