-
Notifications
You must be signed in to change notification settings - Fork 0
Default Values and Comments
getOrSetValueIfAbsent is the startup-safe way to introduce a setting: it writes the default only when the
path is absent, and returns the existing value otherwise.
int port = cfg.getOrSetValueIfAbsent("server.port", 25565); // value only
int max = cfg.getOrSetValueIfAbsent("server.max-players", 20, "cap"); // value + comment
List<String> tags = cfg.getOrSetValueIfAbsent("tags", Arrays.asList("a", "b")); // list overload- When the path is absent, the default is written, the dirty flag and
newDefaultValueToSaveare set, and the default is returned. - When the path is present, the stored value is returned, recast to the default's runtime type — so a
value stored as a
longreads back as theIntegeryour default implies, with noClassCastException.
boolean seeded = cfg.isNewDefaultValueToSave(); // did any default get written this run?
cfg.clearNewDefaultValueToSave();
cfg.setValueIfAbsent("server.motd", "Welcome!"); // thin wrapper over getOrSetValueIfAbsentA key that contains a dot is escaped with a backslash. The path separator is always
., so to address a single key whose name has a dot, escape it asa\.b.cfg.getInt("rates.usd\\.brl")reads the one key"usd.brl"underrates(notrates → usd → brl);\\is a literal backslash. The escape is a no-op for an ordinary key, so plain dotted paths are unaffected. There is no swappable separator.
A comment can be rewritten on every save (documentation in code stays current) or written only once (a
user's edit wins). This applies to both the fluent API and the @Comment annotation.
cfg.setComment("server.port", "the listen port"); // AUTHORITATIVE — overwrites every save
cfg.setDefaultComment("server.port", "tune me"); // SET-IF-ABSENT — kept if one already exists
String c = cfg.getComment("server.port"); // BLOCK comment by default
cfg.getComment("server.port", CommentType.SIDE); // the side commentgetOrSetValueIfAbsent(path, def, comment) uses the set-if-absent rule for the comment.
A scalar list can carry a per-element block comment, addressed by the dotted index — setComment("tags.0", …) comments the first element, "tags.2" the third:
cfg.setComment("tags.0", "the primary tag");Per-element list comments round-trip on YAML and JSONC — JSON and TOML drop them. Object/nested list elements are not addressed this way. See Codecs & Formats.
@Comment(value = "Database settings", mode = CommentMode.SET_IF_ABSENT) // class -> file header
class DbConfig {
@Comment("The JDBC url") // OVERRIDE (default)
String jdbcUrl = "jdbc:h2:mem:test";
@Comment(value = "tune me", mode = CommentMode.SET_IF_ABSENT) // user edit wins
int maxPool = 10;
}-
@Commentdefaults toCommentMode.OVERRIDE— the comment is re-seeded on every binding save, so fixing the text in code reaches existing files. -
@Comment(mode = SET_IF_ABSENT)writes only when the path has no comment yet. - A class-level
@Commentbecomes the file header (at the root). UseSET_IF_ABSENTon the class to keep a header the user wrote.
Comment writes are always safe: on a
NONE-fidelity codec (JSON) the comment is simply not emitted, and the data is never corrupted. See Codecs & Formats.
The comment block at the very top (above the first key) and bottom (below the last) are first-class on the
Config façade, with the same two write modes as field comments:
cfg.setHeader("=== My Plugin ===", "Do not edit while the server runs"); // OVERRIDE
cfg.setDefaultHeader("generated by EveryConfig"); // only if no header yet
cfg.setFooter("end of file"); // the footer counterpart
List<String> header = cfg.getHeader(); // empty when none; clearHeader() removes it- Each argument may contain
\n(split into lines), so a multi-line ASCII-art banner goes in as one argument. - The header never swallows the first key's own comment: a blank line separates them (an empty header line is
emitted as a bare
#///, so only the separator is truly blank). - A class-level
@Commenton a bound POJO seeds the header too (its mode decides override vs set-if-absent);setHeader/setDefaultHeaderfollow the same precedence.
Like field comments, header/footer are held in memory but never written on a
NONE-fidelity codec (JSON).
A file where every comment is glued to the entry above it is hard to read. Two independent mechanisms open it up; both are rendering decisions, and neither is stored in the file as a setting.
cfg.withBlankLineBeforeComments(1); // one blank line above every COMMENTED root key
cfg.withBlankLineBeforeComments(1, 2); // ...reaching down to depth 2 (a top-level section's keys)
cfg.withCommentStyle(CommentStyle.of(2, 1)); // the same thing, spelled out
CommentStyle style = cfg.getCommentStyle();It is a floor, applied at emit time, and it is deliberately narrow:
| It floats a blank line above… | It never touches… |
|---|---|
| an entry that carries a block comment | an entry with no comment |
an entry within maxDepth (1 = root keys) |
anything deeper than maxDepth
|
| an entry that is not the first emitted in its block | the first entry of the document, a section or a [table] — the header or the parent key already separates it |
| — | a list element, the file header, or the footer |
| — | spacing the file already had that is wider (a floor never tightens) |
Like a key-order pin this is an in-memory policy: re-assert it at startup, it survives reload(), and
nothing is written to mark it. It is off by default, so a plain load/save is byte-identical.
Empty lines opening a comment are a spacing request, not comment text. They become blank lines above that entry, at any depth, whether or not the policy is on:
cfg.setComment("DebugMode", "\n\nDebug system."); // two blank lines above DebugMode
cfg.setBlankLinesBefore("DebugMode", 3); // the same thing, without touching the text
int n = cfg.getBlankLinesBefore("DebugMode"); // what is DECLARED for this path
cfg.setBlankLinesBefore("DebugMode", 0); // and this is how you take them awayThe three authoring forms below are identical — the annotation flattens its lines with \n:
@Comment("\n\nEnabled debug modules.")
@Comment({"", "", "Enabled debug modules."})
cfg.setComment("DebugMode.modules", "\n\nEnabled debug modules.");Where both apply, the wider one wins: max(directive, policy).
-
An empty line in the middle of a comment is unchanged — it still emits a bare
#///marker. -
A comment made only of empty lines is not a directive;
setComment(p, "")still emits one bare marker. -
setHeader/setFooterare never directives. -
Writing a comment without a directive does not touch spacing — so a
@CommentOVERRIDE reseed keeps the separation a user typed into the file. With a directive, the author owns it and rewrites it every save. -
Blank lines that came from the file are never re-read as a directive, so a "cushioned" block round-trips as written:
# # Section two # key: 1
-
A list element keeps its own spacing (
setBlankLinesBefore("tags.1", 1)), and no policy floors it.
The ratchet. Once a save has emitted those blank lines they are part of the file, so a later load reads them as that path's own spacing. Turning the policy off, or lowering it, does not take them back —
setBlankLinesBefore(path, 0)does. A plain-text file carries no record of who put a blank line there.
Per codec: YAML, JSONC and TOML honor the policy; JSON has no structure emitter and is unaffected. In TOML "the first entry of a block" follows the emitted order — bare
key = valuepairs come before sub-sections, so a table's first entry is its first scalar.
migrateKey is the explicit rename hook (reconciliation never infers a rename itself). It moves the data, the
key's own block/side comment and its blank-lines-before, and marks the destination persisted so a later seed
won't overwrite the migrated comment.
import br.com.finalcraft.everyconfig.config.MigrationResult;
// rename the old top-level "mysql" section to "database" — safe to run on every startup
MigrationResult r = cfg.migrateKey("mysql", "database");
if (r == MigrationResult.SOURCE_ABSENT) log.warn("nothing to migrate — typo in the source path?");migrateKey returns a MigrationResult so a re-run or a typo is observable (the tree looks the same whether
the source was already moved or never existed):
| Result | Meaning |
|---|---|
MOVED |
the source moved to the destination (r.moved() is true). If the destination already held data, the source overwrote it. |
SAME_PATH |
oldPath equals newPath — nothing to do. |
INVALID_ROOT |
either path is the root, which cannot be migrated. |
ALREADY_MIGRATED |
the source is gone but the destination exists — a benign re-run from an earlier startup. |
SOURCE_ABSENT |
neither side exists — nothing was migrated, often a typo in oldPath. |
It moves the whole comment subtree — the key's own comment and those of every descendant path — so a nested section keeps all of its documentation across the rename.
→ See also The Dynamic API · Annotations
EveryConfig · br.com.finalcraft.everyconfig:everyconfig-core · One config API, every format, comments included · made by Petrus Pradella
Getting Started
Core Concepts
Typed Binding
Operations
Reference
Contributing