-
Notifications
You must be signed in to change notification settings - Fork 0
Lifecycle and Reload
Config.open is the only file constructor; everything else is a method on the returned handle.
Config cfg = Config.open(path, codec);
LoadStatus status = cfg.lastLoadStatus();You can also let open pick the codec from the file's extension (via CodecRegistry.defaults()), taking a
String, File or Path:
Config cfg = Config.open("config.yml"); // .yml -> YamlCodec
Config cfg = Config.open(Paths.get("a.toml")); // .toml -> TomlCodecFail-fast — it never guesses. A missing or unregistered extension throws a
CodecExceptioninstead of falling back to a default format. Pass an explicit codec —Config.open(path, codec)— to override.
Config.open never throws on a bad file — a corrupt config must not block startup:
| Situation | Result | lastLoadStatus() |
|---|---|---|
| File absent | empty tree; first save() creates it |
ABSENT |
| Zero-byte file | empty tree | EMPTY |
| Parse failure | file copied to .bak, empty tree (file not overwritten) |
PARSE_FAILED_BACKED_UP |
| Clean parse | tree loaded | OK |
cfg.save(); // encode (with comments + key order if the codec carries them) + atomic write
cfg.saveIfDirty(); // no I/O when nothing changed since the last load/save
cfg.saveAsync(); // CompletableFuture<Void> on a shared daemon executorWrites go through a fair per-Config lock and an atomic move (.tmp → target), so a reader never sees a torn
file.
How durably each save() must land is fixed at open time by a third open argument:
Config cfg = Config.open(path, codec, BackStore.Durability.FSYNC);BackStore.Durability |
Behavior |
|---|---|
OS_CACHE (default) |
returns once the atomic rename is visible; the bytes may still live only in the OS page cache |
FSYNC |
forces the bytes and the rename to the storage device before returning — slower, but survives an OS/power crash |
An
OS_CACHEwrite never corrupts the previous content (the rename is atomic); a crash can only lose the latest write.FSYNCtrades throughput for surviving that crash.
In-memory save principle. Outside a watcher, a
Configlives entirely in memory:save()never reads the file first — it dumps the in-memory tree. If something edits the file on disk while your app holds the config, that edit is overwritten on the nextsave()unless youreload()to pick it up. Editing a running app's config without a reload is an anti-pattern.
Config.inMemory() builds a Config with no file but the full typed/POJO API — setValue/mergeValue,
getValue(path, type), @Key/@Section/@Comment, enum-by-name, java.time, Optional. It is the right
handle for a transient or test config; save() throws because there is nowhere to write.
Config cfg = Config.inMemory();
cfg.setValue("database.pool.size", 16);
DbSettings db = cfg.getValue("database", DbSettings.class);
// cfg.save(); // throws — no back-store; persist with save(codec) below
Config.inMemory()is not the same as a barenew Config().inMemory()carries a codec, so it accepts any annotation-aware POJO;new Config()has no codec and accepts only native values (scalar /Map/ list /JsonNode).
A file-backed Config can persist or re-target a different format without re-opening:
Config cfg = Config.open("server.yml"); // live codec = YamlCodec
cfg.save(new JsonCodec()); // one-shot: write JSON to the same file; live codec unchanged
cfg.changeCodec(new TomlCodec()); // switch the format used by every subsequent save()
cfg.save(); // now emits TOML
save(codec)andchangeCodec(codec)write through the new format but do not rename the file. Emitting a format the extension does not imply is your call — a later extension-inferredConfig.openwould pick the wrong codec. Switching from a comment-bearing codec (YAML/TOML/JSONC) to JSON (NONE) drops the comment overlay on the next save.
cfg.reload();| Situation | Result | lastLoadStatus() |
|---|---|---|
| Clean parse | replaces the live tree | OK |
| File absent | keeps the live tree | ABSENT |
| Parse failure | keeps the live tree, no backup, no overwrite | PARSE_FAILED_KEPT |
cfg.isDivergedFromDisk(); // true after a PARSE_FAILED_KEPT reload (memory differs from a broken file)
cfg.getLastModified(); // last-write timestamp
cfg.hasBeenModified(); // changed on disk since the last load/save?Poll the file on a daemon thread and refresh the tree when it changes on disk:
cfg.onReload(() -> log.info("config reloaded"))
.withAutoReload(Duration.ofSeconds(2)); // positive duration; closes any prior watcher
// ... later
cfg.stopAutoReload(); // halt polling (a self-save does NOT trigger a self-reload)By default the watcher diffs a cheap (mtime, size) fingerprint. A second boolean argument also hashes the
file content each poll, catching a same-size edit that lands within one coarse mtime tick:
cfg.withAutoReload(Duration.ofSeconds(2), true); // also detect in-place editsThat costs a full read per poll, so it is opt-in. Leave it
false(the one-arg form) unless edits that keep the byte count identical must be picked up.
cfg.close(); // idempotent; stops the watcher. Config is AutoCloseable.try (Config cfg = Config.open(path, codec)) {
cfg.setValue("x", 1);
cfg.save();
} // auto-closed→ See also The Dynamic API · Codecs & Formats
EveryConfig · br.com.finalcraft:EveryConfig · One config API, every format, comments included · made by Petrus Pradella
Getting Started
Core Concepts
Typed Binding
Operations
Reference
Contributing