Skip to content

Quick Start

Petrus Pradella edited this page Jul 1, 2026 · 6 revisions

Quick Start

This walks from an empty file to a typed, documented, reloadable config.

1. Open a config over a file

import br.com.finalcraft.everyconfig.config.Config;
import br.com.finalcraft.everyconfig.codec.jackson.YamlCodec;

import java.nio.file.Paths;

Config cfg = Config.open(Paths.get("server.yml"), new YamlCodec());

Or skip the codec — Config.open(String) (also File/Path) derives it from the file-name extension:

Config cfg = Config.open("server.yml");   // .yml -> YamlCodec, .toml -> TomlCodec, .json -> JsonCodec, ...

This is fail-fast, not magic: a missing or unknown extension throws a CodecException — it never guesses a format. Pass a codec explicitly (open(path, codec)) to override.

Config.open never throws on a bad file: an absent file starts empty, and a malformed one is backed up to .bak and starts empty (see Lifecycle, Reload & Watching).

2. Read and write by path

cfg.setValue("server.host", "localhost");   // auto-vivifies the "server" object
cfg.setValue("server.port", 25565);

String host = cfg.getString("server.host");
int port    = cfg.getInt("server.port", 25565);   // 2nd arg is the default if absent

3. Seed defaults that document themselves

getOrSetValueIfAbsent writes the default (and a comment) only if the path is absent, so it is safe to call on every startup:

int maxPlayers = cfg.getOrSetValueIfAbsent("server.max-players", 20, "hard player cap");
boolean pvp    = cfg.getOrSetValueIfAbsent("server.pvp", true, "allow player-vs-player");

4. Save

cfg.save();   // atomic write; comments and key order preserved

The resulting server.yml:

server:
  host: localhost
  port: 25565
  # hard player cap
  max-players: 20
  # allow player-vs-player
  pvp: true

5. Bind to a typed object (optional)

class ServerConfig {
    public String host = "localhost";
    public int port = 25565;

    @Key(transformCase = KeyTransformCase.KEBAB_CASE)
    public int maxPlayers = 20;     // <-> key "max-players"

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

ServerConfig sc = cfg.loadAs(ServerConfig.class, new YamlCodec());   // bind + run @PostLoad
sc.maxPlayers = 50;
cfg.mergeValue("", sc);               // merge the POJO back into the tree (unknown keys survive; setValue would replace)
cfg.save();

When the config was opened via Config.open(...), getValue binds a subtree without restating the codec — the 2-arg form reuses the one the config carries:

ServerConfig sc = cfg.getValue("server", ServerConfig.class);   // path-scoped loadAs
// "" or null -> the whole tree; an absent path -> the type's defaults

6. Switch format — change one line

Everything above stays identical; only the codec (and extension) change:

Config cfg = Config.open(Paths.get("server.toml"), new TomlCodec());
// or new JsonCodec() / new JsoncCodec()

Already opened over a file? You don't have to reopen to change formats:

cfg.save(new JsonCodec());        // one-shot: write THIS snapshot as JSON; the live codec is unchanged
cfg.changeCodec(new TomlCodec()); // switch the format for every later save() (the file path is untouched)

Need a config with no file at all — a default template, a test fixture? Config.inMemory() gives the full typed/POJO API (path API, getValue(path, type), @Key/@Section/@Comment) but has no back-store, so save() throws:

Config template = Config.inMemory();
template.setValue("server.port", 25565);
ServerConfig sc = template.getValue("server", ServerConfig.class);

A bare new Config() is different: it has no codec, so it accepts only native values (scalars, Map, List, JsonNode) — a POJO setValue/getValue needs a codec, which Config.open(...) or Config.inMemory() supply.

→ Next: Architecture Overview · The Dynamic API

Clone this wiki locally