Skip to content

Config Advanced

This page covers format selection, migrations, adapters, and runtime-aware reload patterns.

Format Selection

If your @ConfigFile uses {ext}, MagicUtils can switch formats using:

  • <config>.format next to the config file
  • magicutils.format in the config root directory
  • -Dmagicutils.config.format=...
  • MAGICUTILS_CONFIG_FORMAT

Supported values include json, jsonc, yml, yaml, and toml depending on the installed format helpers.

If multiple candidate files exist, MagicUtils picks one and logs a warning.

Format Migration

When the selected format changes and an older format file exists, MagicUtils can migrate the data into the new target file. This is useful when moving from JSONC to YAML or TOML without forcing users to recreate their configs.

Schema Migrations

Register ordered ConfigMigration steps to evolve config schemas:

manager.registerMigrations(MyConfig.class,
        new ConfigMigration() {
            public String fromVersion() { return "0"; }
            public String toVersion() { return "1"; }
            public void migrate(Map<String, Object> root) {
                root.put("enabled", true);
            }
        }
);

MagicUtils stores the current schema version in config-version.

Custom Adapters

Use ConfigAdapters.register(...) for custom value types:

ConfigAdapters.register(Duration.class, new ConfigValueAdapter<>() {
    public Duration deserialize(Object value) { ... }
    public Object serialize(Duration value) { ... }
});

Register adapters before the affected config class is first loaded.

Change Subscriptions

Use subscribeChanges(...) when you want an unsubscribe handle:

ListenerSubscription subscription = manager.subscribeChanges(MyConfig.class, (cfg, sections) -> {
    // apply live update
});

Use onChange(...) when you only need fire-and-forget registration.

Runtime Resource Binding

MagicRuntime can rebuild managed resources on matching config changes:

MagicRuntimeConfigBinding<ServiceConfig, ReloadableClient> binding = runtime.bindConfig(
        "service.client",
        ServiceConfig.class,
        config -> new ReloadableClient(config), // ReloadableClient implements AutoCloseable
        "service"
);

Useful properties of this pattern:

  • the latest resource is accessible via binding.require()
  • the same resource is exposed through runtime.requireNamedComponent("service.client", ReloadableClient.class)
  • replaced resources are closed automatically
  • binding.close() removes the named runtime component and stops listening for config changes

Validation And Constraints

@MinValue / @MaxValue

Clamp numeric config values to a safe range on load:

@ConfigValue("interval_seconds")
@MinValue(5)
@MaxValue(3600)
private int intervalSeconds = 30;

Out-of-range values are silently clamped. Set warn = false to suppress the log message.

@SaveTo

Store a field in a separate file:

@ConfigValue("secrets")
@SaveTo("secrets.{ext}")
private Secrets secrets = new Secrets();

@ConfigSerializable

Enable a class for use inside config lists or maps:

@ConfigSerializable
public class ServerEntry {
    @ConfigValue("name")
    private String name = "";
}

@ListProcessor

Apply per-item validation when loading lists:

@ConfigValue("entries")
@ListProcessor(EntryProcessor.class)
private List<ServerEntry> entries = new ArrayList<>();

The processor implements ListItemProcessor<T> and returns ProcessResult.ok(), ProcessResult.modified(value), or ProcessResult.replaceWithDefault().

Hot Reload

Mark reloadable sections and listen for updates:

@ConfigReloadable(sections = {"messages"})
public final class MyConfig { ... }

Use section-aware reloads to avoid rebuilding unrelated services:

manager.reload(MyConfig.class, "messages");
manager.reloadAsync(MyConfig.class, "messages");
manager.reloadSmart(MyConfig.class, "messages");