Configuration and messages

Keystone separates the file on disk from the object the rest of your plugin reads. ManagedConfig owns a YAML file, your parser turns it into an immutable settings object, and Snapshot<T> swaps that object only after a complete load succeeds.

Loading a managed YAML file

Put the default file in src/main/resources, then request it from the handle:

ManagedConfig source = keystone.config("config.yml");

String locale = source.string("locale", "en_US");
boolean debug = source.bool("debug", false);
int limit = source.integer("limits.entries", 100);
List<String> worlds = source.stringList("enabled-worlds");
ConfigurationSection storage = source.section("storage");

On first use, Keystone writes the bundled resource into the plugin data folder. On later loads it keeps the administrator's values and fills missing paths from the bundled defaults. New settings therefore receive their defaults without requiring the user to delete or replace the file.

reload()

Re-read the file and merge missing bundled defaults. This changes the ManagedConfig; it does not validate or replace your own parsed settings object.

save()

Write the current YAML to disk. Use this for commands that intentionally edit configuration.

yaml()

Access Bukkit's YamlConfiguration when the typed convenience methods are not enough.

file()

The resolved file inside the plugin's data folder, useful in error messages.

Publish a complete snapshot

private final Snapshot<Settings> settings = new Snapshot<>(Settings.defaults());

private boolean reloadSettings() {
    ManagedConfig source = keystone.config("config.yml");
    source.reload();

    LoadReport report = new LoadReport();
    Settings candidate = parseSettings(source, report);
    report.print(getLogger(), "settings");
    if (report.hasErrors()) {
        return false;
    }

    settings.set(candidate);
    return true;
}

Readers call settings.get(). Because Snapshot replaces one immutable value through a volatile reference, concurrent readers see the old settings or the new settings—never half of each. Keep the old snapshot active when validation reports errors.

LoadReport distinguishes three outcomes:

  • error: that definition cannot work and should block publishing the candidate.
  • warn: it loaded, but the author should review what was changed or ignored.
  • downgrade: the server lacks a capability and a documented fallback was chosen.

messages.yml

keystone.messages() manages messages.yml and creates MessageService lazily. A minimal file is:

prefix: "<dark_gray>[<gold>Example</gold>]</dark_gray> "
usage: "<gray>Use <yellow>/example help</yellow>."
item-renamed: "<gray>Renamed the item to <white><name></white>."
MessageService messages = keystone.messages();
messages.send(player, "item-renamed", MessageService.value("name", playerInput));
messages.sendActionBar(player, "working");
Component component = messages.get("usage");

send includes the configured prefix; get returns the message itself. Use prefixed when you need the combined component without sending it.

Choose the right placeholder

value(key, text)safe text

Insert text verbatim. MiniMessage tags inside the value are not parsed. This is the default for player input, names, ids and the Map<String, String> overload.

parsed(key, miniMessage)trusted markup

Parse the replacement as MiniMessage. Use only for text controlled by your plugin or by a trusted server administrator.

component(key, component)component

Insert a component already constructed by trusted code.

KeystoneText.parse() accepts trusted MiniMessage. parseUntrusted() uses the restricted safe tag set, escape() neutralises markup characters, and plain() removes formatting from a component.

Reloading messages

messages.reload() re-reads messages.yml. If your plugin exposes one reload command, reload its managed files, validate and publish its settings snapshot, then reload messages. Report a failure without discarding the known-good runtime state.

Missing message keys render as <red>Missing message: <key> instead of disappearing. Keep that visible marker during development, and test both trusted admin-authored markup and a hostile player-supplied value such as a MiniMessage click tag to confirm value(...) remains unparsed.