Skip to main content

Configuration Guide

This document will guide you on how to create a standardized configuration management system based on the Nukkit-MOT framework development specifications, covering core functionalities such as basic configuration reading, nested object handling, and dynamic saving.

Configuration File Structure​

It is recommended to organize configuration files using the YAML format. A typical structure is as follows:

config.yml
this-is-a-key: Hello! Config! # String type configuration
another-key: true # Boolean type configuration
object-key: # Object type configuration
enabled: false
subKey1: nukkit
subKey2: 2023
array-key: # Array type configuration
- first element
- second element
- third element
📁ExamplePlugin-Maven
📁lib
📁src
📁main
📁java
📁resources
📄config.yml
📁target

Configuration Class Implementation​

Basic Configuration Class​

public class ExampleConfig {
private final Config config;
// Use Lombok to automatically generate Getters
@Getter private String aKey;
@Getter private boolean anotherKey;
@Getter private ArrayList<String> arrayKey;
public ExampleConfig() {
// Ensure the configuration file exists (automatically creates an empty configuration)
ExamplePlugin.getInstance().saveResource("config.yml");
// Initialize the configuration object (with default values)
config = new Config(
new File(ExamplePlugin.getInstance().getDataFolder(), "config.yml"),
Config.YAML,
new ConfigSection(new LinkedHashMap<>() {{
// Default value configuration section
put("this-is-a-key", "Hello! Config!");
put("another-key", true);
// Nested object default values
put("object-key", new LinkedHashMap<String, Object>() {{
put("enabled", false);
put("subKey1", "nukkit");
put("subKey2", 2023);
}});
// Array default values
put("array-key", Arrays.asList(
"first element",
"second element",
"third element"
));
}})
);
// Load configuration into memory
setAKey(config.getString("this-is-a-key"));
setAnotherKey(config.getBoolean("another-key"));
setArrayKey((ArrayList<String>) config.getStringList("array-key"));
}
}

Nested Object Handling​

// Define nested configuration objects in the ExampleConfig class
public class KeyObject {
private final ConfigSection configSection;
@Getter private boolean enabled;
@Getter private String subKey1;
@Getter private Integer subKey2;
public KeyObject() {
// Get the object configuration section
this.configSection = config.getSection("object-key");
// Read with default values
this.enabled = configSection.getBoolean("enabled", false);
this.subKey1 = configSection.getString("subKey1", "nukkit");
this.subKey2 = configSection.getInt("subKey2", 2023);
}
// Support chainable set methods
public KeyObject setEnabled(boolean value) {
enabled = value;
configSection.set("enabled", enabled);
return this;// Method Chaining
}
}

Dynamic Configuration Saving​

// Main configuration save method
public void save() {
config.set("this-is-a-key", aKey);
config.set("another-key", anotherKey);
config.set("array-key", arrayKey);
config.save();
}
// Nested object save method
public ExampleConfig save() {
configSection.set("enabled", enabled);
configSection.set("subKey1", subKey1);
configSection.set("subKey2", subKey2);
config.save();
return parent;
}

Best Practices​

  1. Default Value Assurance: Always provide default values in the constructor to prevent configuration file corruption.
  2. Type Safety: Use type-specific methods like getBoolean()/getInt() instead of the generic get().
  3. Configuration Isolation: Use separate configuration classes for managing nested objects.
  4. Memory Caching: Store configurations in memory fields upon first load to avoid frequent file reads.
  5. Ordered Storage: Use LinkedHashMap to maintain the order of configuration items.
  6. Method Chaining: Have setter methods return 'this' to enable fluent interface.

Configuration Hot Reload​

Implement configuration hot updates by listening to server reload commands:

// Register the event in the plugin main class
@EventHandler
public void onReload(ServerCommandEvent event) {
if (event.getCommand().equals("reload example")) {
this.config = new ExampleConfig();// Re-instantiate to reload
getLogger().info("Configuration reloaded!");
}
}
note

Hot reloading may affect running business logic.

Debugging Tips​

Use config.getRootSection().toString() to quickly output the currently loaded full configuration:

getLogger().info("Current configuration state:\n" + config.getRootSection().toString());

Using eu.okaeri.configs (Built-in)​

Nukkit-MOT ships with okaeri-configs (artifact okaeri-configs-yaml-snakeyaml) and uses it internally to manage nukkit-mot.yml. Plugins can use it directly — no extra dependency is required at runtime. Compared with the traditional Config API it offers:

  • POJO-based — fields are the schema, no string keys scattered across the codebase.
  • Strong typing — values are deserialized into actual Java types (including nested objects, generic List/Map).
  • Annotations — @Comment, @Header, @CustomKey produce human-friendly YAML with comments.
  • Orphan cleanup — removeOrphans(true) automatically prunes unknown keys when the schema evolves.
note

Add okaeri-configs-yaml-snakeyaml to your build with <scope>provided</scope> (Maven) or compileOnly (Gradle); the server already provides it at runtime.

Defining a Config Class​

import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
import eu.okaeri.configs.annotation.CustomKey;
import eu.okaeri.configs.annotation.Header;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;

import java.util.ArrayList;
import java.util.List;

@Getter
@Setter
@Accessors(fluent = true)
@Header("########################################")
@Header("ExamplePlugin Configuration")
@Header("########################################")
public class ExampleConfig extends OkaeriConfig {

@Comment("Greeting shown on join")
@CustomKey("welcome-message")
private String welcomeMessage = "Hello! Config!";

@Comment("Whether the feature is enabled")
private boolean enabled = true;

@Comment({"", "Nested settings block"})
@CustomKey("feature-settings")
private FeatureSettings featureSettings = new FeatureSettings();

@Comment("Worlds to apply the feature in")
private List<String> worlds = new ArrayList<>();
}

Nested categories simply extend OkaeriConfig again:

@Getter
@Setter
@Accessors(fluent = true)
public class FeatureSettings extends OkaeriConfig {

@Comment("Cooldown in ticks")
private int cooldown = 20;

@CustomKey("max-uses")
private int maxUses = 10;
}

Loading and Saving​

import eu.okaeri.configs.ConfigManager;
import eu.okaeri.configs.yaml.snakeyaml.YamlSnakeYamlConfigurer;

public class ExamplePlugin extends PluginBase {
private ExampleConfig config;

@Override
public void onEnable() {
java.io.File file = new java.io.File(getDataFolder(), "config.yml");
getDataFolder().mkdirs();

this.config = ConfigManager.create(ExampleConfig.class, it -> {
it.configure(opt -> {
opt.configurer(new YamlSnakeYamlConfigurer());
opt.bindFile(file);
opt.removeOrphans(true); // drop keys no longer defined in the class
});
it.saveDefaults(); // write file with defaults if absent
it.load(true); // load + save back (normalizes comments/order)
});

getLogger().info(config.welcomeMessage());
}

public void updateAndSave() {
config.enabled(false);
config.featureSettings().cooldown(40);
config.save();
}
}

Reload​

public void reload() {
this.config.load(); // re-read the bound file in place
}

When to Choose Which​

  • OkaeriConfig — fixed schema, comments / nested categories, infrequent runtime mutation.
  • Config — dynamic or user-defined keys, runtime-arbitrary structure, or non-YAML formats (PROPERTIES, TXT, TOML, DETECT auto-detection).
tip

Nukkit-MOT itself uses OkaeriConfig for nukkit-mot.yml and Config for server.properties. You can read ServerConfig.java as a real-world reference.