Config Advanced

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:

java
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:

java
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:

java
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:

java
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:

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

@SaveTo

Store a field in a separate file instead of the main config document. The field is excluded from the main file, written to (and loaded back from) the path in the annotation, and multiple fields sharing one @SaveTo path are collected into a single document keyed by each field's config path:

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

Use {ext} so the side file follows the active config format.

@ConfigSerializable

Enable a class for use inside config lists or maps:

java
@ConfigSerializablepublic class ServerEntry {    @ConfigValue("name")    private String name = "";}

Annotation Options

Several annotations carry options that are easy to miss:

AnnotationOptionDefaultEffect
@ConfigValuerequiredfalseFail loading if the key is absent instead of using the field default.
@CommentabovetruePlace the comment above the key; false puts it inline.
@ConfigFileautoCreatetrueWrite the file with defaults on first load when missing.
@ConfigFiletemplate""Seed a new file from a bundled template resource.
@ConfigReloadablenotifyOnChangetrueFire change listeners when a reloadable section changes.
java
@ConfigFile(value = "example.{ext}", template = "example-default.jsonc")@ConfigReloadable(sections = {"messages"}, notifyOnChange = true)public final class ExampleConfig {    @ConfigValue(value = "api-key", required = true)    private String apiKey;    @ConfigValue("greeting")    @Comment(value = "Shown to players", above = true)    private String greeting = "Hello";}

@ListProcessor

Apply per-item validation when loading lists:

java
@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:

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

Use section-aware reloads to avoid rebuilding unrelated services:

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