Lightweight wrapper for ConfigLib with centralized config management. Java 21
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependency>
<groupId>com.github.verschuls</groupId>
<artifactId>YamlFlow</artifactId>
<version>v1.2.4</version>
</dependency>repositories {
maven { url 'https://jitpack.io' }
}
dependencies {
implementation 'com.github.verschuls:YamlFlow:v1.2.4'
}- Hash-based reloading - Only reloads when file content changes
- Async initialization - Non-blocking config loading with CompletableFuture
- Reload callbacks - React to config changes
- Thread-safe - All operations are properly synchronized
- Bulk loading - Load multiple configs from a directory
- Config versioning - Automatic backup and migration on version mismatch
Use CM for named config files (settings.yml, messages.yml, etc.)
public class ServerConfig extends BaseConfig<ServerConfig.Data> {
public ServerConfig(Path dir) {
super(dir, "server", Data.class);
}
@Header("Server Configuration")
@Footer("End of config")
public static class Data extends BaseData {
public String host = "localhost";
public int port = 8080;
}
}
// Register
CM.register(new ServerConfig(Path.of("./config")));
// Wait for init
CM.onInit(ServerConfig.class).thenAccept(data -> {
System.out.println("Server: " + data.host + ":" + data.port);
});
// Get data (after init)
ServerConfig.Data data = CM.get(ServerConfig.class);
// Reload & callbacks
CM.reload(ServerConfig.class);
CM.onReload(ServerConfig.class, data -> System.out.println("Reloaded!"));Add automatic version tracking and backup on version mismatch using the @CVersion annotation. Works with both CM and CMI.
public class VersionedConfig extends BaseConfig<VersionedConfig.Data> {
public VersionedConfig(Path dir) {
super(dir, "config", Data.class);
}
@CVersion("1.0.0")
public static class Data extends BaseData {
public String setting = "default";
}
}Behavior:
- On load, the file's
versionfield is compared against the@CVersionvalue - If versions don't match, the old config is backed up to
old/config-v1.0.0-xxxx.yml - The config is then updated with the new version and default values for new fields
Custom backup directory:
@CVersion(value = "1.0.0", backupDir = "backups")
public static class Data extends BaseData {
// backups will go to backups/config-vX.X.X-xxxx.yml
}Custom version comparison:
// Set custom comparator (called once at startup)
CM.setVersionComparator((fileVersion, configVersion) ->
fileVersion.equals(configVersion) // return true = versions match, skip backup
);Use CMI for loading multiple similar configs from a directory (players/, kits/, etc.)
@Configuration
public class PlayerData extends BaseData {
public String name = "Unknown";
public int level = 1;
}
// Using the builder pattern
CMI<String, PlayerData> players = CMI.newBuilder(
Path.of("./players"),
PlayerData.class,
CIdentifier.fileName()
)
.filter(CFilter.underScores())
.inputNulls(true)
.build();
// Access configs
Optional<PlayerData> player = players.get("steve");
HashMap<String, ConfigInfo<PlayerData>> all = players.get();
// Access config with path info
Optional<ConfigInfo<PlayerData>> info = players.getInfo("steve");
info.ifPresent(i -> {
System.out.println("Path: " + i.getPath());
System.out.println("Name: " + i.getData().name);
});
// Create & save
ConfigInfo<PlayerData> newPlayer = players.create("alex", "alex");
newPlayer.getData().name = "Alex";
players.save("alex", newPlayer.getData());
// Reload
players.reload();
players.onReload(all -> System.out.println("Reloaded " + all.size() + " players"));CMI also supports versioning with @CVersion:
@Configuration
@CVersion(value = "2.0", backupDir = "old_players")
public class PlayerData extends BaseData {
public String name = "Unknown";
public int level = 1;
public int xp = 0; // new field in v2.0
}
// Custom version comparator for this CMI instance
CMI<String, PlayerData> players = CMI.newBuilder(Path.of("./players"), PlayerData.class, CIdentifier.fileName())
.setVersionCompare((fileVersion, configVersion) -> fileVersion.equals(configVersion))
.build();| Method | Description |
|---|---|
filter(CFilter) |
Exclude files from loading |
setVersionCompare(VersionCompare) |
Custom version comparator (default: VersionCompare.basic()) |
inputNulls(boolean) |
Allow null values from YAML |
outputNulls(boolean) |
Write null values to YAML |
acceptNulls(boolean) |
Shorthand for both inputNulls and outputNulls |
addSerializer(Class, Serializer) |
Custom type serializer |
addSerializerFactory(Class, Function) |
Context-aware serializer factory |
setFieldFilter(Predicate<Field>) |
Control which fields are serialized |
setNameFormatter(NameFormatter) |
Custom field-to-YAML-key naming |
Control how configs are identified in CMI:
// File name as key: "player.yml" -> "player"
CIdentifier.fileName()
// Parse file name as UUID: "550e8400-e29b-...yml" -> UUID
CIdentifier.fileNameUUID()
// Auto-increment IDs: 0, 1, 2, ...
CIdentifier.simpleID(new AtomicInteger())
// Custom logic
(file, config) -> config.customIdFilter which files to load:
// Load all files
CFilter.none()
// Skip files like "_template_.yml"
CFilter.underScores()
// Custom filter (return true to exclude)
(file, config) -> file.getName().startsWith("backup")Add headers/footers to generated YAML files:
@Header("=== My Config ===\nEdit with care")
@Footer("Generated by YamlFlow")
public static class Data extends BaseData {
public String value = "default";
}Output:
# === My Config ===
# Edit with care
value: default
# Generated by YamlFlow- Async CMI - Non-blocking bulk config loading?
- Optimizations - Performance improvements?
- CM Properties - Builder pattern for BaseConfig
- ...and more