How to Register Custom Loader Implementations for Additional File Formats in Owner

Register custom Loader implementations in Owner by implementing the Loader interface and calling ConfigFactory.registerLoader() before creating any configuration instances.

The Owner configuration library (matteobaccan/owner) supports property files, XML, and system properties through a pluggable loader abstraction. To handle non-standard formats like YAML, TOML, or INI, you must register custom Loader implementations that teach Owner how to parse these sources. This guide walks through the exact steps based on the current source code implementation.

Understanding the Loader Architecture

Owner delegates all resource loading to classes implementing the Loader interface defined in src/main/java/org/aeonbits/owner/loaders/Loader.java. A valid loader must provide three operations: decide if it can handle a URI via accept(URI), read the resource into a Properties object via load(Properties result, URI uri), and generate a default URI specification via defaultSpecFor(String uriPrefix).

The LoadersManager class in src/main/java/org/aeonbits/owner/LoadersManager.java maintains an ordered list of registered loaders. By default, it instantiates with PropertiesLoader, XMLLoader, and SystemLoader in its constructor. When Owner needs to load a resource, it iterates this list and selects the first loader whose accept() method returns true.

Registration occurs through the ConfigFactory façade (src/main/java/org/aeonbits/owner/ConfigFactory.java), which forwards calls to the singleton Factory instance. According to the source, newly registered loaders are inserted at index 0 of the internal list (loaders.add(0, loader)), giving them priority over built-in loaders.

Implementing the Loader Interface

Create a class implementing org.aeonbits.owner.loaders.Loader to define how Owner should parse your custom format. The implementation must handle URI acceptance, content parsing, and default specification generation.

package org.example.owner.loaders;

import org.aeonbits.owner.loaders.Loader;
import java.io.IOException;
import java.net.URI;
import java.util.Properties;

public class YamlLoader implements Loader {

    @Override
    public boolean accept(URI uri) {
        String path = uri.toString().toLowerCase();
        return path.endsWith(".yaml") || path.endsWith(".yml");
    }

    @Override
    public void load(Properties result, URI uri) throws IOException {
        // Parse YAML content and populate result
        // Example: result.putAll(yamlParser.load(uri.toURL()));
        result.setProperty("yaml.loaded", "true");
    }

    @Override
    public String defaultSpecFor(String uriPrefix) {
        return uriPrefix + ".yaml";
    }
}

Key implementation details:

  • The accept method should perform fast checks (typically file extensions) to avoid I/O overhead during loader selection.
  • The load method receives an empty Properties instance that it must populate; it should throw IOException for any parsing or network errors.
  • The defaultSpecFor method enables shorthand @Source annotations by converting a prefix like "appConfig" into a full URI like "appConfig.yaml".

Registering Your Custom Loader

You must register custom loaders before creating any configuration instances. Use the static ConfigFactory.registerLoader() method for global registration, or instantiate a new Factory for isolated loader sets.

Global Registration via ConfigFactory

import org.aeonbits.owner.ConfigFactory;
import org.example.owner.loaders.YamlLoader;

public class Application {
    public static void main(String[] args) {
        ConfigFactory.registerLoader(new YamlLoader());
        
        // Now YAML files are supported:
        MyConfig config = ConfigFactory.create(MyConfig.class);
    }
}

Instance-Level Registration

For tests or multi-tenant scenarios requiring isolated loaders, use ConfigFactory.newInstance() to obtain a fresh Factory instance:

Factory factory = ConfigFactory.newInstance();
factory.registerLoader(new YamlLoader());
MyConfig config = factory.create(MyConfig.class);

According to src/main/java/org/aeonbits/owner/ConfigFactory.java lines 40-42, passing null to registerLoader throws a NullPointerException, so always provide a valid instance.

Priority and Thread-Safety Considerations

Registration Order Matters. Because LoadersManager adds new loaders at the head of the internal list, your custom implementation takes precedence over built-in loaders with overlapping accept patterns. If you need to extend rather than override behavior, ensure your accept logic is more specific than the default loaders.

Thread Safety. The LoadersManager uses a ReentrantReadWriteLock to protect the loader list. While registration is thread-safe, you should register loaders once during application bootstrap to minimize lock contention. Runtime registration after configs are instantiated is possible but not recommended for performance-critical paths.

Summary

  • Implement the Loader interface from org.aeonbits.owner.loaders to define parsing logic for new file formats.
  • Register implementations via ConfigFactory.registerLoader() before creating any configuration objects to ensure they are available in the LoadersManager list.
  • New loaders are inserted at the front of the list, giving them priority over default loaders like PropertiesLoader and XMLLoader.
  • Use instance-level Factory objects via ConfigFactory.newInstance() when you need isolated loader configurations for testing or specific contexts.
  • Avoid null registrations and perform registration during bootstrap to ensure thread-safe, contention-free initialization.

Frequently Asked Questions

How do I register a custom Loader for YAML files in Owner?

Implement the Loader interface with an accept method checking for .yaml or .yml extensions, then call ConfigFactory.registerLoader(new YourYamlLoader()) before any ConfigFactory.create() calls. Owner will automatically use your loader when encountering matching URIs.

What happens if two Loaders accept the same URI pattern?

Owner selects the first loader in the internal list whose accept() returns true. Since LoadersManager adds new registrations at index 0, custom loaders override built-in ones. Ensure your accept logic is specific enough if you want to avoid shadowing default behavior.

Can I register Loaders after creating configuration instances?

Technically yes, but it is not recommended. The LoadersManager uses a ReentrantReadWriteLock making runtime registration thread-safe, but doing so after configs are instantiated can lead to inconsistent behavior or unnecessary lock contention. Register all loaders during application startup.

What is the difference between ConfigFactory.registerLoader and Factory.registerLoader?

ConfigFactory.registerLoader() is a static convenience method that delegates to the singleton Factory instance, affecting all subsequent ConfigFactory.create() calls. Factory.registerLoader() works on specific Factory instances created via ConfigFactory.newInstance(), allowing isolated loader configurations for specific use cases or tests.

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 →