Skip to content

Configuration

KCommon creates a module's main configuration under:

<plugin data folder>/<module name>/config.yml

On first load, files stored in the plugin jar under src/main/resources/<module name>/ are copied into that directory. Put a default config.yml there when you want to ship comments or structured defaults.

Reflective configuration

Use a ConfigClassSource for fields that should be loaded into the module configuration. Every field must have @Conf because module config sources require the annotation.

package com.example.example.greetings;

import com.golfing8.kcommon.config.generator.Conf;
import com.golfing8.kcommon.config.generator.ConfigClassSource;

public final class GreetingsConfig implements ConfigClassSource {
    @Conf("Text shown when a player is greeted.")
    public static String greetingMessage = "&aWelcome, {PLAYER}!";

    @Conf("Whether the greeting is enabled.")
    public static boolean enabled = true;
}

KCommon maps Java field names to YAML paths, so greetingMessage becomes greeting-message. The defaults are written to config.yml and existing server values are loaded into the fields.

Prefer a built-in adapter when a value has a human-friendly configuration format:

@Conf("How often the greeting cache is refreshed.")
public static TimeLength refreshInterval = TimeLength.parseTime("30m");

This stores a duration such as 30m instead of a raw tick count. Check the config.adapter package before introducing a custom representation.

Build derived state after loading

Keep user-editable values in the config source, then rebuild indexes or lookups from those values during module enable. Unannotated fields in a module config source are not configuration entries, so they can hold derived state:

public final class GreetingsConfig implements ConfigClassSource {
    @Conf("Configured greeting weights.")
    public static Map<String, Integer> weights = new HashMap<>();

    public static Map<String, Integer> normalizedWeights;

    public static void init() {
        normalizedWeights = new HashMap<>();
        weights.forEach((key, value) -> {
            if (value < 0) {
                throw new IllegalArgumentException("Greeting weights cannot be negative");
            }
            normalizedWeights.put(key.toLowerCase(Locale.ROOT), value);
        });
    }
}

Call GreetingsConfig.init() from onEnable() after KCommon has loaded the config. This makes reloads deterministic and keeps derived maps from retaining state from a previous module instance. Validate values in the same phase and let invalid configuration fail module enable rather than silently substituting an unsafe value.

The @Conf annotation also supports a YAML label and a separate module config file:

@Conf(
        value = "The maximum number of greetings to show.",
        label = "max-greetings",
        config = "limits"
)
public static int maxGreetings = 3;

This value is stored in <module data folder>/limits.yml. Keep the configSources entry in @ModuleInfo synchronized with the source class.

Reading structured values

For values with a registered KCommon adapter, load from the module's configuration section:

MenuBuilder menu = getMainConfig()
        .getOrLoad("greeting-menu", MenuBuilder.class)
        .orElseThrow(() -> new IllegalStateException("greeting-menu is missing"));

getOrLoad can load a bundled resource into the data folder when necessary. Use getConfig("name") for an additional YAML file or loadConfigGroup("directory") for a directory of related files.

Before creating a custom adapter, check the config.adapter package for an existing type adapter.

Custom serializable types

Implement CASerializable when a value is a structured object that KCommon does not already support:

public final class GreetingFormat implements CASerializable {
    private String prefix = "&a";
    private String message = "Welcome!";

    @Override
    public void onDeserialize() {
        // Normalize or validate fields after loading when needed.
    }
}

Custom serializable types require a no-argument constructor. Use CASerializable.Options for advanced behavior such as flattened values, delegated paths, config mode, or polymorphic type resolution. When a serializable value is a field in a module ConfigClassSource, that field still needs @Conf because module sources require annotations.

Configuration rules

  • Treat configuration as user input and validate values before using them.
  • Keep Bukkit API access on the server thread unless the operation is safe off-thread.
  • Store only defaults in the jar. User-edited values live in the plugin data folder.
  • Reload configuration through the module lifecycle instead of caching stale values across onDisable().