Configuration
YAML files with bundled defaults, typed getters, object mapping, validation, migrations and saves that keep comments.
bloom-config reads and writes YAML with its own SnakeYAML copy, so it behaves the same on every server. It has no
Bukkit dependency.
Loading a file
YamlConfig config = YamlConfig.builder(plugin.getDataFolder().toPath().resolve("config.yml"))
.defaults(() -> plugin.getResource("config.yml")) // bundled in src/main/resources
.logger(plugin.getLogger())
.build(); // creates the file from defaults if missing, then loads itIf the file does not exist, Bloom copies your bundled config.yml byte for byte, so your comments and layout
reach the server owner unchanged.
Reading values
String prefix = config.getString("messages.prefix"); // required: throws if missing everywhere
int maxHomes = config.getInt("homes.max", 3); // with a fallback
Duration delay = config.getDuration("homes.teleport-delay"); // "3s", "1m30s" or a number of seconds
List<String> worlds = config.getStringList("homes.disabled-worlds");
ConfigSection kits = config.section("kits");
for (String kit : kits.keys()) {
int cost = kits.getInt(kit + ".cost", 0);
// ...
Integer.valueOf(cost).hashCode();
}A value missing from the file falls back to the bundled default. Getters without a fallback argument throw if the
value is missing everywhere. When a section exists in the file, keys() returns only the file's keys, so a kit a
server owner deleted does not come back from the defaults.
Mistakes in the file produce messages that point at the line:
config.yml line 7: 'homes.max-homes' expected a whole number but found 'ten'
Invalid YAML in config.yml at line 12, column 3: mapping values are not allowed here.
Check indentation (spaces only, no tabs) and quoting.Mapping to classes
static final class HomeSettings {
int maxHomes = 3; // key "max-homes"; the value here is the default when the key is missing
Duration teleportDelay = Duration.ofSeconds(3);
List<String> disabledWorlds = new ArrayList<String>();
@ConfigKey("allow-in-combat")
boolean combat = false;
}
static HomeSettings homes(YamlConfig config) {
return config.get("homes", HomeSettings.class);
}Fields map to kebab-case keys (maxHomes becomes max-homes) unless @ConfigKey says otherwise. Missing keys keep
the field's default value. Supported field types: strings, numbers, booleans, characters, enums (case-insensitive),
UUID, Duration, List, Set, Map<String, ...>, nested classes with a no-argument constructor, and anything with a
registered serializer.
Validation
config.validate()
.require("storage.type")
.oneOf("storage.type", "sqlite", "mysql", "mariadb")
.range("homes.max-homes", 1, 100)
.type("homes.teleport-delay", Duration.class)
.check(); // throws ConfigException listing every problem with its line numberAll problems are reported at once, each with its line, so a server owner can fix everything in one edit.
Migrations
return YamlConfig.builder(plugin.getDataFolder().toPath().resolve("config.yml"))
.defaults(() -> plugin.getResource("config.yml"))
.version(3) // files without "config-version" count as version 1
.migration(1, m -> m.move("prefix", "messages.prefix")) // 1 -> 2
.migration(2, m -> m.set("storage.type", m.has("mysql") ? "mysql" : "sqlite")) // 2 -> 3
.logger(plugin.getLogger())
.build(); // old files are backed up as config.yml.v1.<timestamp>.bak before migratingBefore migrating, Bloom copies the file to config.yml.v1.<timestamp>.bak. If a migration throws, the file is left
untouched. A file with a higher version than your plugin understands is refused rather than downgraded.
Reloading
try {
config.reload();
sender.sendMessage("Reloaded.");
} catch (ConfigException e) {
sender.sendMessage("Reload failed, keeping the previous settings: " + e.getMessage());
}reload() swaps in the new values only if the whole file loads and migrates successfully.
Saving
config.set("homes.max-homes", 5);
config.set("spawn.world", "world");
config.save(); // atomic write; comments and untouched values keep their formattingsave() edits the file's YAML structure instead of rewriting it from scratch: comments, blank lines and the formatting
of untouched values stay as the server owner left them. The write is atomic (a temporary file replaces the original),
so a crash never leaves half a file.
New settings in updates
.addMissingDefaults(true) writes settings that exist in your bundled defaults but not in the server's file, with
their comments, after backing the file up. It is off by default; values that are only missing are read from the
defaults anyway.
Custom types
TypeSerializer<Location> locations = new TypeSerializer<Location>() {
@Override
public Location deserialize(Object raw) {
String[] p = raw.toString().split(",");
World world = Bukkit.getWorld(p[0]);
if (world == null) {
throw new IllegalArgumentException("unknown world '" + p[0] + "'");
}
return new Location(
world, Double.parseDouble(p[1]), Double.parseDouble(p[2]), Double.parseDouble(p[3]));
}
@Override
public Object serialize(Location value) {
return value.getWorld().getName() + "," + value.getX() + "," + value.getY() + "," + value.getZ();
}
};
YamlConfig config = YamlConfig.builder(plugin.getDataFolder().toPath().resolve("spawns.yml"))
.serializer(Location.class, locations)
.build();
Location spawn = config.get("spawn", Location.class);Safety
Values are loaded with SnakeYAML's safe constructor (no arbitrary Java objects), duplicate keys are rejected, and alias expansion is limited to block "billion laughs" files.