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 cleanupremoveOrphans(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.