How to Load and Merge XML Property Files Using XMLLoader in Owner

Use Owner's built-in XMLLoader to automatically parse standard Java XML properties or custom hierarchical XML by pointing @Sources to .xml files, and enable merging across multiple files with @LoadType(MERGE).

The Owner library (matteobaccan/owner) provides a type-safe configuration management system for Java applications. When you need to load and merge XML property files using XMLLoader, the framework handles resource detection, SAX-based parsing, and deterministic merging without requiring manual XML parsing code.

Understanding XMLLoader in Owner

XMLLoader is Owner's built-in implementation for reading XML-based configuration files, located at owner/src/main/java/org/aeonbits/owner/loaders/XMLLoader.java. This loader handles both the standard Java XML properties format (produced by java.util.Properties.storeToXML) and custom hierarchical XML structures where nested elements map to dot-notation property keys.

The loader is automatically registered in LoadersManager alongside PropertiesLoader and SystemLoader at lines 40-44 of owner/src/main/java/org/aeonbits/owner/LoadersManager.java. This automatic registration means no additional configuration is required to process XML files.

How XMLLoader Discovers and Parses XML Files

File Discovery via accept()

When Owner initiates resource loading, it queries registered loaders through the accept(URI) method to determine capability. XMLLoader declares XML support by checking that the resource URL ends with the .xml extension, as implemented in lines 29-33 of XMLLoader.java.

Parsing Strategy with XmlToPropsHandler

Once a URI is accepted, the load(Properties, URI) method opens an input stream and instantiates a SAX parser, delegating document processing to the inner class XmlToPropsHandler. This handler builds a flat java.util.Properties map through three distinct parsing modes:

  • Standard Java XML format: Resolves the properties.dtd system identifier (see resolveEntity) and maps each <entry key="...">value</entry> element to a corresponding property entry.
  • Custom hierarchical XML: Constructs property keys by concatenating element names with dots (e.g., <database><url>value</url></database> becomes database.url) and extracts element attributes as separate properties prefixed by the element name.

The conversion logic resides in the startElement, characters, and endElement methods between lines 86-119 of XMLLoader.java.

Merging Multiple XML Property Files

Owner merges multiple XML sources when your configuration interface declares @LoadType(MERGE). This annotation instructs the framework to combine properties from all specified sources in declaration order, rather than stopping at the first available file.

The merge operation executes in PropertiesManager.merge at lines 42-45 of owner/src/main/java/org/aeonbits/owner/PropertiesManager.java. The implementation iterates through loaded property maps and copies all entries into the final result, meaning later sources override earlier ones according to the @Sources declaration order.

Practical Code Examples

Loading a Single XML Configuration

Define an interface referencing your XML file:

import org.aeonbits.owner.Config;
import org.aeonbits.owner.ConfigFactory;
import org.aeonbits.owner.Config.Sources;

@Sources("classpath:app-config.xml")
public interface AppConfig extends Config {
    String dbUrl();
    String dbUser();
    String dbPassword();
}

Instantiate the configuration:

AppConfig cfg = ConfigFactory.create(AppConfig.class);
System.out.println(cfg.dbUrl());  // Outputs value read from app-config.xml

Merging Multiple XML Sources

Enable merging to layer configurations with override capability:

import org.aeonbits.owner.Config.LoadType;
import static org.aeonbits.owner.Config.LoadType.MERGE;

@LoadType(MERGE)
@Sources({
    "classpath:defaults.xml",        // Base configuration (lowest priority)
    "file:${user.home}/app.xml",     // User-specific overrides
    "file:/etc/app.xml"              // System-wide overrides (highest priority)
})
public interface MergedConfig extends Config {
    String host();
    int port();
}
MergedConfig cfg = ConfigFactory.create(MergedConfig.class);
// Returns value from /etc/app.xml if present, 
// otherwise ${user.home}/app.xml, otherwise defaults.xml
System.out.println(cfg.host());

Processing Custom Hierarchical XML

For XML files with nested elements and attributes:

<?xml version="1.0" encoding="UTF-8"?>
<config>
    <mail smtp="smtp.example.com">
        <username>user</username>
        <password>secret</password>
    </mail>
</config>

Map using dot notation for elements and attributes:

import org.aeonbits.owner.Config.Key;

@Sources("file:custom.xml")
public interface MailConfig extends Config {
    @Key("mail.smtp")      
    String smtpHost();   // Maps to smtp attribute value
    
    @Key("mail.username")  
    String username();   // Maps to username element text
    
    @Key("mail.password")  
    String password();
}

The resulting property map contains:

  • mail.smtp = smtp.example.com
  • mail.username = user
  • mail.password = secret

Summary

  • Automatic Detection: XMLLoader automatically handles resources ending in .xml without requiring manual registration, as configured in LoadersManager.
  • Dual Format Support: The XmlToPropsHandler class handles both standard Java XML properties (with properties.dtd resolution) and custom hierarchical XML structures.
  • Hierarchical Mapping: Custom XML elements convert to dot-notation keys (e.g., database.url), while attributes become standalone properties prefixed by their element name.
  • Deterministic Merging: Use @LoadType(MERGE) to combine multiple XML sources, with later files overriding earlier ones according to the implementation in PropertiesManager.merge.

Frequently Asked Questions

Does XMLLoader support XML schemas or validation?

XMLLoader uses a SAX-based parser that recognizes the standard Java properties DTD for validation of standard formats. For custom XML structures, it performs structural parsing without strict schema validation, building property keys from element hierarchies and attributes as implemented in the XmlToPropsHandler class at lines 86-119 of XMLLoader.java.

How do I override XML properties with system properties?

Since XMLLoader is registered alongside SystemLoader in LoadersManager, you can combine @Sources pointing to XML files with the system:properties source. System properties will override XML values when using appropriate load strategies or when explicitly importing system properties in your configuration interface.

Can I mix XML property files with standard .properties files?

Yes. Owner's LoadersManager registers both XMLLoader and PropertiesLoader simultaneously at initialization. You can declare both .xml and .properties files in your @Sources annotation, and the framework automatically selects the appropriate loader for each URI based on file extensions, merging them seamlessly when @LoadType(MERGE) is specified.

What happens if two XML files define the same property key?

When using @LoadType(MERGE), the PropertiesManager.merge method processes sources sequentially in the order they appear in the @Sources annotation. Later entries overwrite earlier ones, so the value from the last XML file in the list takes precedence according to the merge logic at lines 42-45 of PropertiesManager.java.

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 →