Define the field contract
In the Paper 26.3 and Java 25 project, create src/main/resources/config.yml with the line cooldown-seconds: 30. Zero means no wait, no field means 30. The line "30" is wrong: it is not an integer.
In plugin.yml add permissions: → trailnotes.config: → default: op. command /checkconfig is registered with code; Administrative rights are not issued to an ordinary player. The resource is copied for the first time via saveDefaultConfig.
Parse the candidate separately from live state
Replace the main class with the full code. Unlike the convenient static loader, the YamlConfiguration instance and its load allow you to handle a read error. Javadoc YAML also warns that this object is not synchronized: it is not passed between threads here.
package ru.example.trailnotes;
import java.io.File;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
public final class TrailNotesPlugin extends JavaPlugin {
private record Settings(int cooldownSeconds) {}
private Settings settings = new Settings(30);
private Settings readSettings() throws Exception {
YamlConfiguration yaml = new YamlConfiguration();
yaml.load(new File(getDataFolder(), "config.yml"));
Object raw = yaml.contains("cooldown-seconds")
? yaml.get("cooldown-seconds") : Integer.valueOf(30);
if (!(raw instanceof Integer value) || value < 0 || value > 3600)
throw new IllegalArgumentException("cooldown-seconds: нужен integer 0..3600");
return new Settings(value);
}
@Override public void onEnable() {
saveDefaultConfig();
try {
settings = readSettings();
registerCommand("checkconfig", (source, args) -> checkConfig(source.getSender()));
} catch (Exception error) {
getLogger().severe("config.yml не прошёл проверку: " + error.getClass().getSimpleName());
getServer().getPluginManager().disablePlugin(this);
}
}
private void checkConfig(CommandSender sender) {
if (!sender.hasPermission("trailnotes.config")) {
sender.sendMessage("Нет права trailnotes.config.");
return;
}
try {
Settings candidate = readSettings();
settings = candidate;
sender.sendMessage("Применено: " + settings.cooldownSeconds() + " секунд.");
} catch (Exception error) {
sender.sendMessage("Отказ. Проверьте cooldown-seconds: integer 0..3600.");
sender.sendMessage("Прежнее значение: " + settings.cooldownSeconds() + " секунд.");
}
return;
}
}Verify that an invalid reload changes nothing
After assembly and installation, execute checkconfig in the console, change the runtime file plugins/TrailNotes/config.yml and repeat command. The resource inside the source does not change the existing server file.
| File | Result |
|---|---|
| cooldown-seconds: 0 | Applied 0 |
| cooldown-seconds: "30" | Refusal; remains 0 |
| cooldown-seconds: -1 | Refusal; remains 0 |
| cooldown-seconds: 3601 | Refusal; remains 0 |
| cooldown-seconds: 60 | Applied 60 |
| Field missing | default 30 applied |
Check component settings before replacing
For the arena, check min-players <= max-players, the presence of a world, the uniqueness of the ID and all links to rewards. Collect messages with field paths, then either replace one immutable snapshot or leave the same one entirely. A round that has already started may retain its old model until completion.
When changing a schema, keep the schema-version number and make a copy of the file before migration. The new template should not erase custom values. Do not include connection passwords in diagnostic messages.
The state model shows when it is acceptable to apply a new set. Save the settings of the started round.
The binary version and its old data file must be checked together. Check the config after replacing the JAR.
