Skip to content

Default Values and Comments

Petrus Pradella edited this page Jun 27, 2026 · 5 revisions

Default Values & Comments

Self-seeding defaults

getOrSetDefaultValue 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.getOrSetDefaultValue("server.port", 25565);              // value only
int max  = cfg.getOrSetDefaultValue("server.max-players", 20, "cap");   // value + comment

List<String> tags = cfg.getOrSetDefaultValue("tags", Arrays.asList("a", "b")); // list overload
  • When the path is absent, the default is written, the dirty flag and newDefaultValueToSave are 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 long reads back as the Integer your default implies, with no ClassCastException.
boolean seeded = cfg.isNewDefaultValueToSave();   // did any default get written this run?
cfg.clearNewDefaultValueToSave();
cfg.setDefaultValue("a.b", 5);                    // thin wrapper over getOrSetDefaultValue

Comments — two write modes

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.

Fluent

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 comment

getOrSetDefaultValue(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.

Annotation

@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;
}
  • @Comment defaults to CommentMode.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 @Comment becomes the file header (at the root). Use SET_IF_ABSENT on 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.

Moving a key — migrateKey

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.

cfg.migrateKey("oldName", "newName");

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

Clone this wiki locally