-
Notifications
You must be signed in to change notification settings - Fork 0
FAQ
You probably saved with the JSON codec, whose comment fidelity is NONE — JSON has no comment syntax, so
comments are accepted in memory but not emitted. Use YAML, TOML or JSONC if you need comments on disk. See
Codecs & Formats.
Outside a watcher, a Config lives in memory and save() never reads the file first — it dumps memory. Call
reload() (or re-open()) before saving if you want a manual edit to survive. See
Lifecycle, Reload & Watching.
TOML has no null type. The TomlCodec omits a null-valued key on write, so it reads back as absent
(the typed getter returns its default). If you need an explicit null on disk, use YAML, JSON or JSONC.
The TOML reader mis-parses integers beyond the 64-bit range (and some near it). To keep the value intact, the
TomlCodec writes such an integer as a quoted string — the numeric getters still read it as digits
(getLong/getString both work), and on YAML/JSON it stays a number. This only affects values around 1e18
and larger.
It won't — lenient bind (the default) keeps the field's real default: the bad node is removed from a
working copy of the tree and the bind is retried. Check binder.lastLoadIssues() to see what was skipped, or
use STRICT to fail fast. See Entity Binding.
It shouldn't — a binding save (via EntityBinder.write) merges: the tree wins and unknown keys survive.
If you explicitly set ObsoletePolicy.REMOVE, that strips keys the schema no longer declares — that's opt-in
and destructive. The default is PRESERVE.
That's expected — setValue(path, pojo) replaces the subtree at path: only the keys the POJO declares
remain, so a key (and its comment) the POJO doesn't declare is dropped. To keep unknown keys, use
mergeValue(path, pojo) — it merges the POJO into the subtree, and the tree wins for keys the POJO
doesn't declare. (Sibling keys outside path are untouched either way.)
cfg.setValue("legacy.extra", "kept");
cfg.mergeValue("", new DbConfig()); // unknown keys survive; setValue("", ...) would drop legacy.extraBind with ObsoletePolicy.COMMENT_OUT — it keeps the key (the data survives, as with PRESERVE) but stamps
an authoritative deprecation block comment on it, overwriting any stale comment it carried.
BindOptions opts = BindOptions.defaults()
.withObsoletePolicy(BindOptions.ObsoletePolicy.COMMENT_OUT);LOSSLESS codecs only. On a codec that can't round-trip a comment losslessly (JSON =
NONE, JSONC =LOSSY) it degrades toPRESERVE: the key is kept, but no marker is written.
It should — @Section relocates a field of a nested POJO too, with the path taken relative to that POJO's
own location. The one unsupported spot is a @Section field inside a List/Map element (a collection
element has no stable path). A sectioned type also honors ObsoletePolicy.REMOVE like any other. See
Annotations.
Any numeric getter — they tolerate a number stored as a string: getLong("1700000000000"),
getInt("25565"), getDouble("3.14") all parse. An empty string reads as the default.
That's the trichotomy: an explicit null is present (contains is true) but a typed getter flattens it to
the default. Use getValue/getNode to tell a real value from null. See The Dynamic API.
By design. getString on an object node returns the default (an object is not a string in any useful
sense — same rule as the numeric getters returning their default on a type mismatch). An array still
joins its elements with newlines (handy for a lines: block), and a scalar reads as its canonical text.
cfg.getString("database"); // database is a section -> null (or your default)
cfg.getString("messages.kick"); // an array -> "line 1\nline 2"
cfg.getString("server.host", "0.0.0.0"); // a scalar -> "127.0.0.1"No — both overloads are tolerant, like the numeric getters. A malformed or absent value yields null
(getUUID(path)) or your default (getUUID(path, def)); neither ever throws.
UUID owner = cfg.getUUID("world.owner"); // absent/garbage -> null
UUID owner = cfg.getUUID("world.owner", CONSOLE_UUID); // ...or your fallbackThe path separator is fixed at ., but you can escape a dot that belongs to the key itself with a
backslash — a\.b addresses the single key "a.b". \\ is a literal backslash, and the escaping is a
no-op for an ordinary path.
// the tree has rates: { "usd.brl": 5.12 }
double r = cfg.getDouble("rates.usd\\.brl"); // reads the single key "usd.brl" under "rates"There is no swappable separator —
PathOptionsonly exposes'.'. Escaping is the mechanism for a dotted key.
migrateKey(old, new) is safe to run unconditionally on every startup, and it returns a
MigrationResult so a typo or a benign re-run is observable:
| Result | Meaning |
|---|---|
MOVED |
the source was moved to the destination (if the destination already held a value, the source overwrites it) |
SAME_PATH |
old and new are the same path — nothing to do |
INVALID_ROOT |
either side is the root, which cannot be migrated |
ALREADY_MIGRATED |
source gone, destination present — the migration ran on an earlier startup (benign) |
SOURCE_ABSENT |
neither side exists — nothing migrated, often a typo in old (worth logging) |
if (cfg.migrateKey("mysql.host", "database.host") == MigrationResult.SOURCE_ABSENT) {
log.warn("nothing at mysql.host to migrate — check the path");
}Yes — Config.inMemory() gives the full typed/POJO API (setValue/mergeValue, getValue(path, type),
@Key/@Section/@Comment, enum-by-name, java.time, Optional) with no file behind it, so save()
throws. (A bare new Config() has no codec at all and accepts only native scalar/Map/list/JsonNode
values.) To persist, open a real file instead. See The Dynamic API.
Two hooks, both on a Config opened over a file:
-
save(Codec)— a one-shot persist through another codec; the codec theConfigkeeps is unchanged. -
changeCodec(Codec)— switch the format every latersave()uses.
Config cfg = Config.open("server.yml"); // YAML on disk
cfg.save(new JsonCodec()); // one-shot JSON snapshot; live codec stays YAML
cfg.changeCodec(new TomlCodec()); // every future save() now emits TOMLThe file extension is not rewritten, so emitting a format the name does not imply is your call — a later extension-inferred
Config.openwould pick the wrong codec.
The YAML comment parser is line-based and covers common block-style YAML. A # inside a |/> block scalar,
anchors/merge-keys, explicit ? : keys, or multi-line flow can mis-attach a comment. The data is unaffected;
only the comment overlay for those exotic shapes is best-effort.
No — it never sniffs content. The single-arg overloads derive the codec from the file-name extension via
CodecRegistry.defaults().forFile(...) and fail fast: a missing or unregistered extension throws a
CodecException. Pass a codec explicitly to override or to use an unconventional name:
Config c = Config.open("settings.yml"); // codec from ".yml"
Config c = Config.open(Paths.get("settings.cfg"), new YamlCodec()); // explicitSee Lifecycle, Reload & Watching.
Not in the library — EveryConfig ships thin. A Bukkit/Spigot plugin that bundles it should relocate
com.fasterxml.jackson and org.yaml.snakeyaml in its own shade step. See Installation.
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