ConfigLib is an advanced, type-safe Configuration API designed to simplify configuration management for Bukkit and Forge developers.
- Type-Safety Configuration Handling
Ensures configuration values are used in a type-safe manner directly within your code, reducing potential runtime errors and improving maintainability. - Automatic JSON Mapping
Automatically maps configuration data to Java objects using JSON, eliminating the need for manual parsing and data transformation. - Automatic Configuration Reloading
Monitors configuration files and reloads them automatically when changes are detected, ensuring your application always works with the latest settings. - Automatic Configuration Saving
Automatically saves updated configuration values to disk, preventing data loss and ensuring persistence. - Command Generation for Configuration Management
Seamlessly integrates with CommandLib to generate commands for managing configurations via the command line. - Schema Migration
Built-in versioned migration system allows safe evolution of configuration structure across releases — handling field renames, type changes, and validation constraint changes without breaking existing user data. - Change History & Undo
Every configuration change is automatically recorded with a timestamp. You can browse the history via command and revert to any previous state.
- Java 11 or later
To ensure stability, we recommend replacing latest.release with a specific version such as 0.16.0.
You can find the latest version on
the CommandLib Release Page
and ConfigLib Release Page.
Bukkit (Groovy DSL)
plugins {
id "com.gradleup.shadow" version "8.3.5"
}
repositories {
maven { url 'https://jitpack.io' }
}
dependencies {
implementation "com.github.Maru32768.CommandLib:bukkit:latest.release"
implementation 'com.github.Maru32768.ConfigLib:bukkit:latest.release'
}
shadowJar {
archiveFileName = "${rootProject.name}-${project.version}.jar"
relocate "net.kunmc.lab.commandlib", "${project.group}.${project.name.toLowerCase()}.commandlib"
relocate "net.kunmc.lab.configlib", "${project.group}.${project.name.toLowerCase()}.configlib"
}
tasks.build.dependsOn tasks.shadowJarBukkit (Kotlin DSL)
plugins {
id("com.gradleup.shadow") version "8.3.5"
}
repositories {
maven { url = uri("https://jitpack.io") }
}
dependencies {
implementation("com.github.Maru32768.CommandLib:bukkit:latest.release")
implementation("com.github.Maru32768.ConfigLib:bukkit:latest.release")
}
tasks.named<ShadowJar>("shadowJar") {
archiveFileName.set("${rootProject.name}-${project.version}.jar")
relocate("net.kunmc.lab.commandlib", "${project.group}.${project.name.lowercase()}.commandlib")
relocate("net.kunmc.lab.configlib", "${project.group}.${project.name.lowercase()}.configlib")
}
tasks.named("build") { dependsOn(tasks.named("shadowJar")) }Forge (Groovy DSL)
plugins {
id "com.gradleup.shadow" version "8.3.5"
}
repositories {
maven { url 'https://jitpack.io' }
}
dependencies {
implementation "com.github.Maru32768.CommandLib:forge:latest.release"
implementation "com.github.Maru32768.ConfigLib:forge:latest.release"
}
shadowJar {
archiveFileName = "${rootProject.name}-${project.version}.jar"
dependencies {
include(dependency("com.github.Maru32768.CommandLib:forge:.*"))
include(dependency("com.github.Maru32768.ConfigLib:forge:.*"))
}
relocate "net.kunmc.lab.commandlib", "${project.group}.${project.name.toLowerCase()}.commandlib"
relocate "net.kunmc.lab.configlib", "${project.group}.${project.name.toLowerCase()}.configlib"
finalizedBy("reobfShadowJar")
}
reobf {
shadowJar {
}
}Forge (Kotlin DSL)
plugins {
id("com.gradleup.shadow") version "8.3.5"
}
repositories {
maven { url = uri("https://jitpack.io") }
}
dependencies {
implementation("com.github.Maru32768.CommandLib:forge:latest.release")
implementation("com.github.Maru32768.ConfigLib:forge:latest.release")
}
tasks.named<ShadowJar>("shadowJar") {
archiveFileName.set("${rootProject.name}-${project.version}.jar")
dependencies {
include(dependency("com.github.Maru32768.CommandLib:forge:.*"))
include(dependency("com.github.Maru32768.ConfigLib:forge:.*"))
}
relocate("net.kunmc.lab.commandlib", "${project.group}.${project.name.lowercase()}.commandlib")
relocate("net.kunmc.lab.configlib", "${project.group}.${project.name.lowercase()}.configlib")
finalizedBy("reobfShadowJar")
}
reobf {
create("shadowJar")
}Defining Configuration Classes
public final class TestConfig extends BaseConfig {
public final IntegerValue integerValue = new IntegerValue(10);
public final StringValue stringValue = new StringValue("testValue");
public TestConfig(Plugin plugin) {
super(plugin);
initialize();
}
}Generating and Registering Configuration Commands
public final class TestPlugin extends JavaPlugin {
public void onEnable() {
TestConfig testConfig = new TestConfig(this);
Command root = new Command("test") {
};
// The following commands will be generated:
// /test config get <key> - Gets a specific configuration value.
// /test config list - Gets all configuration values.
// /test config modify <key> <value> - Sets a specific configuration value.
// /test config reload - Reloads the configuration file. You may not need it because there's automatic reloading.
root.addChildren(new ConfigCommandBuilder(testConfig).build());
CommandLib.register(this, root);
}
}Registering Multiple Configurations to Commands
public final class TestPlugin extends JavaPlugin {
public void onEnable() {
TestConfigA testConfigA = new TestConfigA(this);
TestConfigB testConfigB = new TestConfigB(this);
Command root = new Command("test") {
};
root.addChildren(new ConfigCommandBuilder(testConfigA).addConfig(testConfigB)
.build());
CommandLib.register(this, root);
}
}Listening to Configuration Changes
public final class TestConfig extends BaseConfig {
public final IntegerValue integerValue = new IntegerValue(10).onModify(x -> {
System.out.println("Changed integerValue to " + x);
});
public TestConfig(Plugin plugin) {
super(plugin);
initialize();
}
}Defining Custom Value Classes
In this section, we explain how to implement a custom SingleValue class and a custom ListValue class, based on the
following example class.
// Represents a custom data structure with an integer and a string.
// Used as a value in the configuration.
class TestClass {
private final int n;
private final String s;
public TestClass(int n, String s) {
this.n = n;
this.s = s;
}
@Override
public String toString() {
return "TestClass{" + "n=" + n + ", s='" + s + '\'' + '}';
}
}import java.util.List;
// Custom SingleValue implementation for TestClass.
// Allows storing and manipulating a single instance of TestClass in configurations.
public final class TestClassValue extends SingleValue<TestClass, TestClassValue> {
public TestClassValue(TestClass initialValue) {
super(initialValue);
}
@Override
protected List<ArgumentDefinition<TestClass>> argumentDefinitions() {
List<ArgumentDefinition<TestClass>> res = new ArrayList<>();
// Defines the arguments required to construct a TestClass instance.
res.add(new ArgumentDefinition(new IntegerArgument("n"), new StringArgument("s"), (n, s, ctx) -> {
// Converts command arguments to a TestClass instance.
return new TestClass(n, s);
}));
return res;
}
// Converts a TestClass instance to its string representation.
// This string will be used for command completion suggestions and for get/list command results.
@Override
protected String valueToString(TestClass testClass) {
return testClass.toString();
}
}// Custom ListValue implementation for TestClass.
// Allows managing a list of TestClass instances in configurations.
public final class TestClassListValue extends ListValue<TestClass, TestClassListValue> {
public TestClassListValue(List<TestClass> initialValue) {
super(initialValue);
}
@Override
protected List<ArgumentDefinition<List<TestClass>>> argumentDefinitionsForAdd() {
List<ArgumentDefinition<List<TestClass>>> res = new ArrayList<>();
// Defines the arguments required for the Add command.
// These arguments will be used to construct a new TestClass instance and add it to the list.
res.add(new ArgumentDefinition(new IntegerArgument("n"), new StringArgument("s"), (n, s, ctx) -> {
// Converts command arguments to a new TestClass instance and adds it to the list.
return Collections.singletonList(new TestClass(n, s));
}));
return res;
}
@Override
protected List<ArgumentDefinition<List<TestClass>>> argumentDefinitionsForRemove() {
List<ArgumentDefinition<List<TestClass>>> res = new ArrayList<>();
// Defines the arguments required for the Remove command.
// These arguments will be used to identify which TestClass instance to remove from the list.
res.add(new ArgumentDefinition(new StringArgument("target", opt -> {
opt.suggestionAction(sb -> {
for (TestClass v : value) {
sb.suggest(v.toString());
}
});
}, StringArgument.Type.PHRASE_QUOTED), (input, ctx) -> {
// Finds a TestClass instance to remove based on user input.
TestClass target = value.stream()
.filter(x -> x.toString()
.equals(input))
.findFirst()
.orElseThrow(() -> new InvalidArgumentException(input + " is invalid"));
return Collections.singletonList(target);
}));
return res;
}
// Converts a TestClass instance to its string representation.
// This string will be used for command completion suggestions and for get/list command results.
@Override
protected String elementToString(TestClass testClass) {
return testClass.toString();
}
}These custom Value classes can be used in the same way as built-in Value classes.
public final class TestConfig extends BaseConfig {
public final TestClassValue testClassValue = new TestClassValue(null);
public final TestClassListValue testClassListValue = new TestClassListValue(new ArrayList<>());
public TestConfig(Plugin plugin) {
super(plugin);
initialize();
}
}Schema Migration
ConfigLib provides a versioned migration system to evolve your configuration schema safely across releases.
Migrations are registered via Option and run automatically when an older config file is loaded.
The current version is stored as _version_ in the JSON file.
public final class MyConfig extends BaseConfig {
public final StringValue message = new StringValue("hello");
public final EnumSetValue<EntityType> spawnTypes = new EnumSetValue<>(EntityType.class);
public MyConfig(Plugin plugin) {
super(plugin, opt -> opt
// Version 1: rename field
.migration(1, ctx -> {
ctx.rename("msg", "message");
})
// Version 2: type change (IntegerValue -> StringValue)
.migration(2, ctx -> {
if (ctx.has("cooldown")) {
ctx.setString("cooldown", String.valueOf(ctx.getInt("cooldown")));
}
})
// Version 3: remove value that no longer passes validation
.migration(3, ctx -> {
EnumSetValue<EntityType> types = ctx.getObject("spawnTypes",
new TypeToken<EnumSetValue<EntityType>>() {
}.getType());
types.remove(EntityType.GIANT);
ctx.setObject("spawnTypes", types);
}));
initialize();
}
}MigrationContext provides the following operations:
| Method | Description |
|---|---|
has(key) |
Check if a key exists |
getString(key) / getInt(key) / getDouble(key) / getBoolean(key) |
Read primitive values |
getObject(key, Class<T>) |
Read a complex value by class |
getObject(key, Type) |
Read a generic complex value (e.g. with TypeToken) |
setString(key, value) / setInt / setDouble / setBoolean |
Write primitive values |
setObject(key, value) |
Write a complex value |
rename(from, to) |
Rename a field |
remove(key) |
Remove a field |
Migrations only run on existing files that have an older version. New installations start at the latest version and skip all migrations.
- Calling
initialize()initialize()must be called at the end of the concrete config class constructor, after all fields have been assigned. This ensures that the initial config load and file watching start only after the subclass is fully constructed.public final class TestConfig extends BaseConfig { public final IntegerValue integerValue = new IntegerValue(10); public TestConfig(Plugin plugin) { super(plugin); initialize(); // Must be the last statement } }
- Asynchronous Change Detection
Change detection, including when modifying values with the
setmethod, is handled asynchronously. Keep this in mind to avoid race conditions in your application logic.
ConfigCommandBuilder automatically generates a command tree from your config classes.
The examples below use /config as the root command name (set via .name("config")).
Config-level subcommands
These subcommands operate on the config as a whole.
| Command | Description |
|---|---|
/config list |
Show all field values |
/config reload |
Reload from file |
/config reset |
Reset all fields to their default values |
Use .disableList() / .disableReload() / .disableReset() on ConfigCommandBuilder to suppress any of these.
With multiple configs, each subcommand also accepts a config name:
| Command | Description |
|---|---|
/config list <configName> |
List fields for that config |
/config reload <configName> |
Reload that config |
/config reset <configName> |
Reset that config |
/config <configName> |
List fields for that config (alias) |
Per-field subcommands
These subcommands are generated for each Value field.
<field> is the Java field name, or the name set via .entryName().
Get
| Command | Description |
|---|---|
/config <field> |
Show the current value (hover for description) |
/config <configName>.<field> |
Same, always available even with multiple configs |
SingleValue — set
| Command | Description |
|---|---|
/config <field> <value> |
Set the value (shorthand) |
/config <field> set <value> |
Set the value |
/config <field> reset |
Reset to the default value |
NumericValue — arithmetic (IntegerValue, DoubleValue, FloatValue) — extends SingleValue, so set and reset also
apply
| Command | Description |
|---|---|
/config <field> inc |
Increment by 1 |
/config <field> inc <amount> |
Increment by amount (capped at max) |
/config <field> dec |
Decrement by 1 |
/config <field> dec <amount> |
Decrement by amount (floored at min) |
CollectionValue — add / remove / clear (ListValue, SetValue)
| Command | Description |
|---|---|
/config <field> add <element> |
Add an element |
/config <field> remove <element> |
Remove an element |
/config <field> clear |
Remove all elements |
/config <field> reset |
Reset to the default value |
Disable individual operations with .disableAdd() / .disableRemove() / .disableClear().
MapValue — put / remove / clear
| Command | Description |
|---|---|
/config <field> put <key> <value> |
Add or update an entry |
/config <field> remove <key> |
Remove an entry |
/config <field> clear |
Remove all entries |
/config <field> reset |
Reset to the default value |
Disable individual operations with .disablePut() / .disableRemove() / .disableClear().
Use .disableGet() / .disableModify() on ConfigCommandBuilder to suppress get or modify commands globally.
Change history, undo, and diff
Every time a configuration value is modified, the new state is automatically saved to a history file
(<configName>.history.json) alongside the config file. The history persists across server restarts and is capped at
50 entries by default (override createConfigStore() to change this).
History uses 0-based indexing where [0] is the current (latest) state.
Single config
| Command | Description |
|---|---|
/config history |
List all entries with timestamps (hover to preview values) |
/config history <index> |
Show field values at that index |
/config history diff <index> |
Diff current state vs history entry |
/config history diff <index1> <index2> |
Diff between two history entries |
/config history undo |
Revert to the previous state |
/config history undo <N> |
Revert N steps back |
/config undo |
Revert to the previous state |
/config undo <N> |
Revert N steps back |
/config diff <index> |
Diff current state vs history entry |
/config diff <index1> <index2> |
Diff between two history entries |
Multiple configs — prefix with the config name using either order:
| Command | Description |
|---|---|
/config history <configName> |
List entries for that config |
/config history <configName> <index> |
Show field values at that index |
/config history <configName> diff <index> |
Diff for that config |
/config history <configName> undo [N] |
Undo for that config |
/config undo <configName> [N] |
Undo for that config |
/config diff <configName> <index> |
Diff for that config |
/config <configName> history [index] |
Alternative prefix order |
/config <configName> undo [N] |
Alternative prefix order |
/config <configName> diff <index> |
Alternative prefix order |
diff shows only fields that differ, formatted as fieldName: <old> → <new>.
To hide history commands from the generated command tree:
public final class MyConfig extends BaseConfig {
public MyConfig(Plugin plugin) {
super(plugin);
disableHistory();
initialize();
}
// ...
}Use .disableHistory() on ConfigCommandBuilder to suppress history commands globally.
If you use Claude Code, a /configlib skill is available to help you write ConfigLib code.
It loads the API into context and provides usage conventions tailored to library consumers.
Setup: Copy .claude/skills/configlib/ from this repository into your project's .claude/skills/ directory.
Usage:
/configlib How do I define a config with an IntegerValue and a StringValue?
/configlib Write a custom SingleValue for org.bukkit.World