Skip to content

Lifecycle Listeners

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

Register listeners on the builder, or on the holder after creation, to react to specific config events:

// Register on the builder (before creation)
ConfigHolder<MyModConfig> holder = EasyConfig.holder(MyModConfig.class)
    .modId("mymod")
    .onUpdate(config -> hudRenderer.setScale(config.hudScale))
    .onLoad(config -> LOGGER.info("config loaded from disk"))
    .onSave(config -> LOGGER.debug("config saved"))
    .onReset(config -> LOGGER.info("config reset to defaults"))
    .create();

// Register on the holder after creation — useful for addons and extensions
holder.onUpdate(config -> myAddon.onConfigChanged(config));

Each hook fires only for its specific operation:

Hook Fires after
onUpdate An accepted update or updateAndSave
onLoad A successful load (not on a fallback-to-defaults load)
onSave A successful save, updateAndSave, or resetAndSave
onReset An accepted reset or resetAndSave

When listeners do not fire: on a rejected update, on a load that fell back to defaults, and on the build-time load that create() performs during construction (the holder does not yet exist to attach listeners to, and nothing has changed from the caller's perspective).

Listener contract:

  • Receive the published state — the same object data() returns at that moment. It is safe to read but should not be mutated or retained beyond the listener body. Call copy() if values need to outlive the callback.
  • Run on the thread that performed the operation. For createAsync() that is the config worker — calling a blocking holder method from inside a listener on that thread will deadlock and is reported as ConfigError.BLOCKING_CALL_ON_CONFIG_THREAD.
  • A listener that throws is logged as ConfigError.CHANGE_LISTENER_FAILED and skipped. It cannot fail the triggering operation or prevent later listeners from running.

Clone this wiki locally