# How to Load Properties from Classpath, File System, and Custom URIs in OWNER

> Load OWNER properties from classpath file system and custom URIs using @Config.Sources. Implement Loader interface for proprietary schemes.

- Repository: [Matteo Baccan/owner](https://github.com/matteobaccan/owner)
- Tags: how-to-guide
- Published: 2026-03-07

---

**Use the `@Config.Sources` annotation with `classpath:`, `file:`, or custom protocol prefixes, and implement the `Loader` interface to add support for proprietary URI schemes.**

The OWNER library—maintained in the [matteobaccan/owner](https://github.com/matteobaccan/owner) repository—provides a type-safe way to map Java properties files to interfaces. Understanding how to load properties from classpath, file system, and custom URIs allows you to externalize configuration while keeping your code clean and portable.

## Loading Properties from the Classpath

### Using the classpath: Protocol

OWNER treats the `classpath:` prefix as a first-class citizen. When you annotate an interface with `@Config.Sources({"classpath:path/to/file.properties"})`, the library delegates to `ConfigURIFactory.newURI(String)` in [[`ConfigURIFactory.java`](https://github.com/matteobaccan/owner/blob/main/ConfigURIFactory.java)](https://github.com/matteobaccan/owner/blob/master/owner/src/main/java/org/aeonbits/owner/ConfigURIFactory.java). This method strips the prefix and invokes `ClassLoader.getResource()` to locate the resource relative to the root of the classpath (typically `src/main/resources` or inside a JAR).

```java
import org.aeonbits.owner.Config;
import org.aeonbits.owner.ConfigFactory;

@Config.Sources({"classpath:org/aeonbits/owner/config.properties"})
public interface ServerConfig extends Config {
    @Key("server.host")
    String host();
    
    @Key("server.port")
    int port();
}

// Usage
ServerConfig cfg = ConfigFactory.create(ServerConfig.class);
System.out.println(cfg.host() + ":" + cfg.port());

```

### Handling Spaces in Paths

The classpath loader correctly handles paths containing spaces. In [[`LoadPathsWithSpacesTest.java`](https://github.com/matteobaccan/owner/blob/main/LoadPathsWithSpacesTest.java)](https://github.com/matteobaccan/owner/blob/master/owner/src/test/java/org/aeonbits/owner/loadstrategies/LoadPathsWithSpacesTest.java), the test demonstrates loading `classpath:org/aeonbits/owner/directory with spaces/simple.properties`. `ConfigURIFactory` normalizes back-slashes to forward-slashes and preserves the space characters, which the class loader resolves correctly.

## Loading Properties from the File System

### Using the file: Protocol with Variable Expansion

To load from an absolute or relative file path, use the `file:` prefix. `ConfigURIFactory` expands `${...}` variables (such as `${user.dir}` or custom properties) before creating the URI. It also encodes spaces to `%20` to ensure valid URI syntax.

```java
@Config.Sources({
    "file:${user.dir}/config/app.properties",
    "file:/etc/myapp/production.properties"
})
public interface AppConfig extends Config {
    @Key("database.url")
    String dbUrl();
}

```

`PropertiesManager` (in [[`PropertiesManager.java`](https://github.com/matteobaccan/owner/blob/main/PropertiesManager.java)](https://github.com/matteobaccan/owner/blob/master/owner/src/main/java/org/aeonbits/owner/PropertiesManager.java)) reads the `@Sources` annotation, iterates over each string, and delegates to `ConfigURIFactory` to produce a `java.net.URI` object. These URIs are then passed to `LoadersManager` for resolution.

## Loading Properties from Custom URIs

When the built-in `classpath:` and `file:` schemes are insufficient, you can implement custom protocols (such as `zookeeper:`, `consul:`, or `https:` with special handling) by providing your own `Loader`.

### Implementing the Loader Interface

The contract is defined in [[`Loader.java`](https://github.com/matteobaccan/owner/blob/main/Loader.java)](https://github.com/matteobaccan/owner/blob/master/owner/src/main/java/org/aeonbits/owner/loaders/Loader.java). A valid implementation must provide three methods:

- `boolean accept(URI uri)` – returns `true` if this loader handles the given scheme.
- `void load(Properties result, URI uri)` – reads the resource and populates the `Properties` instance.
- `String defaultSpecFor(String uriPrefix)` – returns a default URI string for the given prefix.

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

public class HttpLoader implements Loader {

    @Override
    public boolean accept(URI uri) {
        return "http".equals(uri.getScheme()) || "https".equals(uri.getScheme());
    }

    @Override
    public void load(Properties result, URI uri) throws IOException {
        // Implementation would perform HTTP GET and parse response
        // For demonstration, we inject a dummy value:
        result.setProperty("remote.config", "loadedFromHttp");
    }

    @Override
    public String defaultSpecFor(String uriPrefix) {
        return "https://default.example.com/config";
    }
}

```

### Registering Your Custom Loader

Before creating any configuration interface that uses the custom scheme, register the loader with `ConfigFactory`. This updates the internal `LoadersManager` registry (see [[`LoadersManager.java`](https://github.com/matteobaccan/owner/blob/main/LoadersManager.java)](https://github.com/matteobaccan/owner/blob/master/owner/src/main/java/org/aeonbits/owner/LoadersManager.java)).

```java
import org.aeonbits.owner.ConfigFactory;

// Register once at application startup
ConfigFactory.registerLoader(new HttpLoader());

// Now the factory can resolve https:// URIs
@Config.Sources({"https://api.example.com/config/app.properties"})
public interface RemoteConfig extends Config {
    String remoteConfig();
}

```

### Real-World Example: ZooKeeperLoader

The *owner-extras* module provides a concrete reference implementation. [[`ZooKeeperLoader.java`](https://github.com/matteobaccan/owner/blob/main/ZooKeeperLoader.java)](https://github.com/matteobaccan/owner/blob/master/owner-extras/src/main/java/org/aeonbits/owner/loaders/ZooKeeperLoader.java) implements the `zookeeper:` protocol. It accepts URIs with that scheme, connects to the ZooKeeper ensemble, reads the znode data, and loads it into a `Properties` object. This demonstrates how to integrate OWNER with external configuration stores.

## Summary

- **Classpath loading** uses the `classpath:` prefix and resolves resources via the class loader, supporting spaces in paths.
- **Filesystem loading** uses the `file:` prefix with optional variable expansion (`${user.dir}`) handled by `ConfigURIFactory`.
- **Custom protocols** require implementing the `Loader` interface, registering it via `ConfigFactory.registerLoader()`, and referencing the new scheme in `@Config.Sources`.
- The architecture delegates URI creation to `ConfigURIFactory`, loader selection to `LoadersManager`, and property reading to specific `Loader` implementations such as `PropertiesLoader`, `XMLLoader`, or your custom class.

## Frequently Asked Questions

### Can I load from multiple sources at once?

Yes. The `@Config.Sources` annotation accepts an array of URI strings. OWNER loads them in the order declared, and later sources override earlier ones for duplicate keys. This is managed internally by `PropertiesManager` which iterates over the source list and merges properties into a single configuration instance.

### How does OWNER resolve conflicting properties from different sources?

OWNER uses a **first-declared, last-wins** strategy. When `LoadersManager` processes the list of URIs from `@Sources`, it loads each resource sequentially into the same `Properties` object. If a key exists in multiple sources, the value from the last loaded source overwrites the previous value. You can verify this behavior in the loading logic within `PropertiesManager`.

### Can I use environment variables in file paths?

Yes. `ConfigURIFactory` performs variable expansion on source strings before converting them to URIs. You can reference any system property or environment variable using the `${variable.name}` syntax. For example, `file:${user.home}/app/config.properties` resolves to the user's home directory, and `file:${ENV_CONFIG_PATH}` expands to the value of the `ENV_CONFIG_PATH` environment variable if defined.

### Is it possible to reload properties at runtime?

OWNER creates immutable configuration instances by default, but you can achieve dynamic reloading by creating a new instance via `ConfigFactory.create()` whenever you need fresh values. For true hot-reloading, you would need to implement a custom `Loader` that caches values with a TTL or watches the underlying resource (file, HTTP endpoint, etc.) for changes, then exposes a method to invalidate the cache before re-creating the config interface.