How to Implement the Reloadable Interface for Manual Property Reload in Owner

Extend Reloadable alongside Config and call reload() on your configuration instance to refresh properties at runtime without restarting your application.

The Owner library provides type-safe access to Java properties through dynamic proxies. When you need to pick up external configuration changes on demand, you must implement the Reloadable interface for manual property reload capabilities. This approach complements automatic hot-reload features by giving you explicit control over exactly when configuration refreshes occur.

Understanding the Reloadable Interface

The Reloadable interface in org.aeonbits.owner extends Config and declares three methods that Owner implements dynamically at runtime. When you invoke ConfigFactory.create(), the framework generates a proxy that handles these methods—you provide no method bodies.

The interface defines these operations:

  • void reload() – Re-executes the loading pipeline to refresh cached property values.
  • void addReloadListener(ReloadListener listener) – Registers a callback invoked after each reload.
  • void removeReloadListener(ReloadListener listener) – Unregisters a previously added listener.

According to the source code in [Reloadable.java](https://github.com/matteobaccan/owner/blob/master/owner/src/main/java/org/aeonbits/owner/Reloadable.java), these methods enable programmatic control over configuration refresh cycles while maintaining type safety.

Step-by-Step Implementation

Follow these steps to implement the Reloadable interface for manual property reload:

  1. Define your interface by extending both Config and Reloadable.
  2. Annotate with sources using @Sources to specify property file locations.
  3. Create the proxy via ConfigFactory.create(MyConfig.class).
  4. Trigger reloads by calling cfg.reload() when external changes occur.
  5. Manage observers by registering ReloadListener instances to react to refresh events.

Code Examples

Basic Reloadable Configuration

Define an interface that extends Reloadable and map your properties:

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

@Sources("file:./my.properties")
public interface MyConfig extends Config, Reloadable {
    int timeout();
    String endpoint();
}

Use the configuration and trigger manual reloads at runtime:

import org.aeonbits.owner.ConfigFactory;

public class ConfigDemo {
    public static void main(String[] args) {
        MyConfig cfg = ConfigFactory.create(MyConfig.class);
        System.out.println("Initial timeout: " + cfg.timeout());

        // External process modifies ./my.properties...
        
        cfg.reload();  // Manual reload
        System.out.println("After reload: " + cfg.timeout());
    }
}

This pattern mirrors the implementation verified in [ReloadTest.java](https://github.com/matteobaccan/owner/blob/master/owner/src/test/java/org/aeonbits/owner/reload/ReloadTest.java#L63-L66).

Reloading with Mutable Properties

When importing properties programmatically, reload() refreshes from the current state of the Properties object:

Properties overrides = new Properties();
overrides.setProperty("timeout", "30");

MyConfig cfg = ConfigFactory.create(MyConfig.class, overrides);
System.out.println(cfg.timeout());  // Output: 30

overrides.setProperty("timeout", "45");
cfg.reload();  // Forces re-read of imports
System.out.println(cfg.timeout());  // Output: 45

This technique is demonstrated in ReloadTest.testReloadWithImportedProperties.

Observing Reload Events

Register listeners to execute logic when configurations refresh:

import org.aeonbits.owner.event.ReloadListener;
import org.aeonbits.owner.event.ReloadEvent;

ReloadListener logger = event -> System.out.println(
    "Reload performed on " + event.getSource());

MyConfig cfg = ConfigFactory.create(MyConfig.class);
cfg.addReloadListener(logger);

cfg.reload();  // Listener invoked
cfg.removeReloadListener(logger);
cfg.reload();  // Listener not invoked

The ReloadEvent object provides access to the source configuration instance via getSource(). This behavior is verified in [ReloadTest.java](https://github.com/matteobaccan/owner/blob/master/owner/src/test/java/org/aeonbits/owner/reload/ReloadTest.java#L8-L26).

Combining with Automatic Hot-Reload

Combine Reloadable with the @HotReload annotation for hybrid refresh strategies:

import org.aeonbits.owner.Config.HotReload;

@Sources("file:./dynamic.properties")
@HotReload(2)  // Check every 2 seconds
public interface LiveConfig extends Config, Reloadable {
    String value();
}

When using @HotReload, Owner automatically invokes reload() on the schedule you specify. You can still call reload() manually for immediate updates. This interaction is documented in [reload.md](https://github.com/matteobaccan/owner/blob/master/owner-site/site/docs/reload.md#L18-L30).

Internal Architecture of Manual Reload

When you invoke reload(), Owner delegates to HotReloadLogic, the same component used by the automatic hot-reload feature. The process follows this sequence:

  1. Pipeline Re-execution: Owner re-runs the loading logic, including source resolution, variable expansion, and type conversion.
  2. Cache Update: The dynamic proxy updates its internal Properties snapshot with new values.
  3. Listener Notification: For each registered ReloadListener, Owner fires a ReloadEvent containing the configuration source.

This architecture ensures that manual reloads behave identically to automatic ones, maintaining consistency across the configuration lifecycle.

Summary

  • Extend Reloadable alongside Config to enable manual reload capabilities in your configuration interfaces.
  • Call reload() to refresh properties on-demand without recreating the configuration instance.
  • Use addReloadListener() and removeReloadListener() to observe reload events and trigger side effects.
  • The implementation is generated at runtime by ConfigFactory.create(); you define only the interface structure.
  • Manual reload shares the same HotReloadLogic path as automatic reloading, ensuring consistent behavior.

Frequently Asked Questions

Do I need to implement the methods in the Reloadable interface?

No. The Owner framework generates the implementation dynamically when you call ConfigFactory.create(). The proxy handles reload(), addReloadListener(), and removeReloadListener() internally using the loading pipeline defined by your @Sources annotations.

Can I use Reloadable with immutable Properties objects?

Yes, but you will only see value changes if the underlying source data changes. If you pass a Properties object to ConfigFactory.create(), calling reload() re-reads from that same object. For immutable sources like classpath files, ensure the file content changes on disk before invoking reload().

How do I unregister a ReloadListener?

Call removeReloadListener(ReloadListener listener) on your configuration instance, passing the same listener object you previously registered via addReloadListener(). This is useful for preventing memory leaks when configuration objects have long lifecycles but listeners become obsolete.

What is the difference between @HotReload and manual reload?

The @HotReload annotation schedules automatic reload() calls at a fixed interval, while manual reload requires explicit invocation of the method. You can use both simultaneously: automatic reload handles periodic updates, while manual reload provides immediate consistency when you know external changes have occurred.

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 →