# How to Use the Owner ConfigFactory API to Create and Manage Configuration Instances

> Discover how to use the Owner ConfigFactory API to create and manage type-safe configuration instances. Leverage property loading, variable expansion, and hot-reload features for robust configuration management.

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

---

**The Owner ConfigFactory API provides a static singleton factory to instantiate type-safe configuration objects from annotated interfaces, handling property loading, variable expansion, and hot-reload capabilities.**

The Owner library from the [matteobaccan/owner](https://github.com/matteobaccan/owner) repository eliminates boilerplate configuration code by mapping Java interfaces directly to property files. At the heart of this library lies the **ConfigFactory API**, which serves as the central entry point for creating and managing configuration instances. This article explores how to leverage `ConfigFactory` to build type-safe config objects, customize loading behavior, and handle dynamic updates.

## Architecture Overview

The `ConfigFactory` implementation relies on several key components defined in the `owner/src/main/java/org/aeonbits/owner/` package:

- **Config** – The marker interface that every configuration interface must extend. It defines the annotation model (`@Sources`, `@DefaultValue`, `@Key`, `@HotReload`) and loading policies (`LoadPolicy`, `LoadType`). Source: [`owner/src/main/java/org/aeonbits/owner/Config.java`](https://github.com/matteobaccan/owner/blob/main/owner/src/main/java/org/aeonbits/owner/Config.java).

- **ConfigFactory** – The static singleton factory that provides the public API for creating configurations, storing global properties, and registering extensions. Key methods include `create()`, `setProperty()`, `registerLoader()`, and `setTypeConverter()`. Source: [`owner/src/main/java/org/aeonbits/owner/ConfigFactory.java`](https://github.com/matteobaccan/owner/blob/main/owner/src/main/java/org/aeonbits/owner/ConfigFactory.java).

- **Factory (Internal)** – The actual builder implementation (hidden behind `ConfigFactory`) that parses `@Sources` annotations, applies load policies, and generates dynamic proxies. This is instantiated as a singleton `INSTANCE` via `ConfigFactory.newInstance()`.

- **Loaders** – Pluggable components that read specific URI protocols (e.g., `file:`, `classpath:`). Registered via `ConfigFactory.registerLoader()`.

- **Converters** – Components that transform raw `String` property values into custom Java types. Managed via `ConfigFactory.setTypeConverter()`.

The factory maintains a singleton `Factory` instance (`INSTANCE`) created by `ConfigFactory.newInstance()`, which initializes a daemon `ScheduledExecutorService` for hot-reload functionality and an empty `Properties` object for global state.

## Creating Configuration Instances

The primary entry point is `ConfigFactory.create()`, which validates input parameters and delegates to the internal factory to build a proxy implementation of your configuration interface.

```java
// 1️⃣ Define the interface
@Sources("classpath:server.properties")
public interface ServerConfig extends Config {
    int port();
    String hostname();
    @DefaultValue("10")
    int maxThreads();          // default used if property missing
}

// 2️⃣ Create and use the config
public class MyApp {
    public static void main(String[] args) {
        ServerConfig cfg = ConfigFactory.create(ServerConfig.class);
        System.out.println("Server " + cfg.hostname() + ":" + cfg.port()
                           + " (maxThreads=" + cfg.maxThreads() + ')');
    }
}

```

In [`owner/src/main/java/org/aeonbits/owner/ConfigFactory.java`](https://github.com/matteobaccan/owner/blob/main/owner/src/main/java/org/aeonbits/owner/ConfigFactory.java), the `create()` method validates that any supplied import maps contain no null keys or values before forwarding the request to `INSTANCE.create()`. The factory then reads the `@Sources` URI(s), applies the chosen `LoadPolicy`, and maps interface methods to property values.

## Managing Global Properties and Variable Expansion

You can define global properties that `ConfigFactory` uses to expand `${…}` placeholders inside `@Sources` URIs or property values. Set these **before** creating your configuration instance.

```java
@Sources("file:${confDir}/app.properties")
public interface AppConfig extends Config {
    String dbUrl();
}

// Before creating the config, set the variable that will be expanded:
ConfigFactory.setProperty("confDir", "/opt/app/conf");

// Now the file URI resolves to "/opt/app/conf/app.properties"
AppConfig cfg = ConfigFactory.create(AppConfig.class);
System.out.println(cfg.dbUrl());

```

The `setProperty()` method stores key-value pairs in the factory's global `Properties` object, which the internal parser consults during URI expansion. This allows you to externalize path configurations or environment-specific variables without hardcoding them in annotations.

## Extending the API with Custom Loaders and Converters

The ConfigFactory API supports pluggable loaders and type converters to handle non-standard protocols and complex data types.

### Registering Custom Loaders

Implement the `Loader` interface to support new URI schemes, then register it with the factory:

```java
public class JsonLoader implements Loader {
    @Override public boolean accepts(URI uri) {
        return "json".equalsIgnoreCase(uri.getScheme());
    }
    @Override public void load(Properties props, URI uri) throws IOException {
        // parse JSON file and fill `props`
    }
}

// Register the loader once (typically at application start‑up)
ConfigFactory.registerLoader(new JsonLoader());

// Now you can use a JSON source:
@Sources("json:/etc/app/config.json")
public interface JsonConfig extends Config {
    String apiKey();
}

```

The `registerLoader()` method forwards the custom loader to the singleton factory, enabling resolution of proprietary or non-standard resource protocols.

### Adding Custom Type Converters

For properties that map to complex Java types, register a converter:

```java
public class InetAddressConverter implements Converter<InetAddress> {
    @Override public InetAddress convert(Method method, String value) throws Exception {
        return InetAddress.getByName(value);
    }
}

// Register the converter for InetAddress.class
ConfigFactory.setTypeConverter(InetAddress.class, InetAddressConverter.class);

@Sources("classpath:net.properties")
public interface NetConfig extends Config {
    InetAddress host();   // will be converted automatically
}

```

The `setTypeConverter()` method stores the converter in the factory's registry, allowing automatic type transformation when the configuration proxy invokes methods returning the registered type.

## Enabling Hot-Reload for Dynamic Configuration

For configurations that must reflect runtime changes to underlying property files, use the `@HotReload` annotation. This triggers the `ScheduledExecutorService` daemon (created in `ConfigFactory.newInstance()`) to poll the file system at the specified interval.

```java
@HotReload(2)                     // check every 2 seconds
@Sources("file:/tmp/dynamic.properties")
public interface DynamicConfig extends Config {
    String message();
}

// The config will automatically reflect changes made to the file.
DynamicConfig cfg = ConfigFactory.create(DynamicConfig.class);
while (true) {
    System.out.println("Current: " + cfg.message());
    Thread.sleep(5000);
}

```

The reload mechanism lives in the internal `Factory` implementation, which re-reads the file and updates the backing `Properties` object without requiring application restart.

## Summary

- Use `ConfigFactory.create(MyConfig.class)` to instantiate type-safe configuration objects from interfaces extending `Config`.
- Set global variables with `ConfigFactory.setProperty()` *before* creation to enable `${variable}` expansion in `@Sources` URIs.
- Register custom `Loader` implementations via `registerLoader()` to support non-standard URI schemes like JSON or HTTP.
- Plug in custom type converters using `setTypeConverter()` to map properties to complex Java types automatically.
- Enable dynamic updates by annotating interfaces with `@HotReload` and specifying a poll interval in seconds.

## Frequently Asked Questions

### What is the difference between ConfigFactory and the Factory interface in Owner?

`ConfigFactory` is the static singleton facade that provides the public API, while `Factory` is the internal interface implemented by `DefaultFactory` that handles the actual proxy creation and property loading. All calls to `ConfigFactory.create()` delegate to the singleton `INSTANCE` created via `ConfigFactory.newInstance()`.

### How does ConfigFactory handle concurrent access to configuration instances?

The `ConfigFactory` itself is thread-safe as it uses a singleton `Factory` instance initialized with a thread-safe `ScheduledExecutorService` for hot-reload operations. Configuration proxies created by the factory are immutable and thread-safe for reading, though hot-reload updates happen asynchronously through the scheduler daemon.

### Can I use multiple property sources with different protocols in one configuration interface?

Yes. The `@Sources` annotation accepts an array of URI strings (e.g., `@Sources({"classpath:defaults.properties", "file:${user.home}/app.properties"})`). The `ConfigFactory` processes these according to the specified `LoadPolicy` (FIRST, MERGE, or OVERRIDE) defined in the interface or annotation.

### Where does ConfigFactory store global properties set via setProperty()?

Global properties are stored in a `Properties` object held by the singleton `Factory` instance inside `ConfigFactory`. These properties are consulted during URI expansion when creating configuration instances, as implemented in [`owner/src/main/java/org/aeonbits/owner/ConfigFactory.java`](https://github.com/matteobaccan/owner/blob/main/owner/src/main/java/org/aeonbits/owner/ConfigFactory.java).