Skip to content

Annotations

Petrus Pradella edited this page Jun 30, 2026 · 6 revisions

Annotations

EveryConfig's binding annotations live in br.com.finalcraft.everyconfig.annotation. They layer on top of plain Jackson, so native @Json* annotations keep working too (see Entity Binding).

@Key — rename + case transform

Renames a field's key and/or applies a case transform. Resolution order: @Key@JsonProperty → field name.

@Key(transformCase = KeyTransformCase.KEBAB_CASE)
public int maxPoolSize;          // -> "max-pool-size"

@Key("custom-host")
public String host;              // -> "custom-host"

@Key(transformCase = KeyTransformCase.SNAKE_CASE)
public int ttlSeconds;           // -> "ttl_seconds"

KeyTransformCase: NONE, KEBAB_CASE, SNAKE_CASE, CAMEL_CASE, UPPER_CAMEL_CASE.

Class-wide naming via @JsonNaming

Apply one case to every property of a class instead of annotating each field. Use Jackson's @JsonNaming with the ready-made KeyCaseStrategy.Kebab / KeyCaseStrategy.Snake (in br.com.finalcraft.everyconfig.binding.introspect):

@JsonNaming(KeyCaseStrategy.Kebab.class)
class PoolConfig {
    public int maxPoolSize = 10;     // -> "max-pool-size"
    public String serverHost = "h";  // -> "server-host"

    @Key("HOST")                     // a field-level @Key overrides the strategy
    public String legacy = "x";      // -> "HOST" (verbatim)
}

It is opt-in per class (not installed globally), so only annotated classes are affected. A field-level @Key always wins — @Key("literal") keeps its name verbatim and @Key(transformCase = ...) applies its own case.

@Comment — documentation, two modes

On a field it seeds a block comment; on a type it seeds the file header. String[] value = multi-line.

@Comment({"First line", "second line"})        // OVERRIDE (default): re-seeded every save
public int timeout = 30;

@Comment(value = "tune me", mode = CommentMode.SET_IF_ABSENT)   // written only if absent
public int retries = 3;
  • CommentMode.OVERRIDE (default) — a doc fix in code propagates to existing files.
  • CommentMode.SET_IF_ABSENT — a user-edited comment wins.
  • A class-level @Comment becomes the file header (use SET_IF_ABSENT to preserve a user's header).
  • On a NONE-fidelity codec the seed is a no-op (no comment emitted). See Default Values & Comments.

@Section — place a field under a nested path

@Section("database.pool")
@Key(transformCase = KeyTransformCase.KEBAB_CASE)
public int maxSize = 50;          // written at database.pool.max-size, surfaced back on read

A flat field is relocated to the nested path on write and brought back on read. The path is relative to the field's owner, so a @Section field of a nested POJO is relocated under that POJO's own location too.

class Server {
    @Section("network.tuning")
    public int backlog = 128;     // a Server at "server" -> server.network.tuning.backlog
}

Not inside a List/Map element. A @Section on a field of a collection element is unsupported — a collection element has no stable path. A sectioned type honors ObsoletePolicy.REMOVE (it is no longer forced to PRESERVE).

@KeyIndex — collection indexing

Marks the field whose value becomes the section key when a collection of the type is stored via setValue and read via getList (the keyed layout is automatic — there are no dedicated methods).

class Account {
    @KeyIndex public String name;
    public int balance;
}

Exactly one @KeyIndex per class; valid types are String, the boxed/primitive numerics, boolean, and UUID. See @KeyIndex Collections.

Lifecycle hooks — @PreLoad / @PostLoad / @PreSave / @PostSave

A method (no args, or a single ConfigContext) invoked once around a bind. @PostLoad is the most common — use it to validate or derive state after the tree is read into the entity. The ConfigContext carries the bound section() and, only at @PostLoad, the lenient-bind issues().

class Cfg {
    public int port = 25565;

    @PostLoad
    void validate() {
        if (port < 1 || port > 65535) throw new IllegalStateException("bad port");
    }

    @PostLoad
    void check(ConfigContext ctx) {        // ctx.issues() are the lenient-bind issues (PostLoad only)
        if (!ctx.issues().isEmpty()) log.warn("config had {} issues", ctx.issues().size());
    }
}
  • The four hooks fire around the POJO ⇄ tree binding (read/write), not the file flush.
  • Inherited hook methods (super + sub) all run, de-duplicated by method name.
  • An exception thrown inside is wrapped in a BindException (a BindException thrown inside propagates as-is).
  • The opt-in ConfigLifecycle interface offers the same four hooks for types you cannot annotate.

→ See also Entity Binding · @KeyIndex Collections

Clone this wiki locally