-
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 only — JSON, TOML and JSONC 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).
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 · One config API, every format, comments included · made by Petrus Pradella
Getting Started
Core Concepts
Typed Binding
Operations
Reference
Contributing