Skip to content

Holder Implementation

Gustavo Malvestiti edited this page Aug 16, 2026 · 1 revision

The terminal builder method you call determines the holder implementation. All three expose the same ConfigHolder API; createAsync() returns the wider AsyncConfigHolder which adds non-blocking *Async variants.

Method Where work runs Thread safety Best fit
create() Calling thread, inline Confine to one thread (e.g. the server thread) Regular runtime config
createAsync() Shared config worker thread Safe from any thread Config accessed from multiple threads
createImmutable() Frozen at build time Read-only, safe from any thread Startup-only config
// Single-threaded — all operations run on whichever thread calls them
ConfigHolder<MyModConfig> simple = EasyConfig.holder(MyModConfig.class)
    .modId("mymod")
    .create();

// Thread-safe — operations are submitted to the worker; blocking callers wait
AsyncConfigHolder<MyModConfig> async = EasyConfig.holder(MyModConfig.class)
    .modId("mymod")
    .createAsync();

// Immutable — loaded once during create; update and reset are refused
ConfigHolder<MyModConfig> immutable = EasyConfig.holder(MyModConfig.class)
    .modId("mymod")
    .createImmutable();

Async: the blocking methods (load, save, update, updateAndSave) submit work to the worker and join. Calling them from inside a config hook or lifecycle listener will deadlock and is reported as ConfigError.BLOCKING_CALL_ON_CONFIG_THREAD. Use the *Async variants (loadAsync, saveAsync, updateAsync, updateAndSaveAsync) when you are already on the config thread or simply do not want to block. Those methods are exclusive to AsyncConfigHolder.

Immutable: update, reset, and their variants are refused through the update failure policy — they throw under STRICT and return a rejected UpdateResult under FALLBACK. load and save still work: a reload replaces the published state wholesale.

Clone this wiki locally