How to reload configuration at runtime
1. Run the command
/kreiscraft reloadRequired permission: kreiscraft.admin (granted to ops by default).
Can be run in-game or from the server console.
On success you see core.reload.success. Reload is not transactional: modules that reload successfully keep their new config even if another module fails.
2. What the reload does
ReloadCommand executes three steps in order:
| Step | Method | Effect |
|---|---|---|
| 1 | ConfigService.reload() |
Re-reads config.yml from disk into a new ConfigurationNode. |
| 2 | MessageService.reload() |
Re-reads the active lang file (e.g. lang/de.yml). |
| 3 | ModuleRegistry.reloadConfigs(newRoot) |
Calls onConfigReload(moduleNode) on every enabled module, passing only that module’s sub-node (modules.<id>). Modules are called in the order they were enabled. |
If onConfigReload throws for a particular module, ModuleRegistry.reloadConfigs() catches it, logs at WARNING, and continues with the remaining modules.
3. What the reload does NOT do
- Does not enable or disable modules. A module that was disabled before the reload stays disabled. New
enabled: trueentries take effect only on the next server start. - Does not restart the server.
- Does not restart the Javalin HTTP server — unless
host,port, orcors-originschanged, in which caseApiModule.onConfigReload()stops and restarts Javalin automatically. All other API config fields (heartbeat-interval,async-timeout, etc.) require a full server restart. - Does not reschedule repeating tasks. Modules that hold a scheduler task read the new values but keep the old schedule. For example,
tablisthot-reloads its header/footer templates, but a changedupdate-intervalonly takes effect after a restart.
4. Implementing onConfigReload in a module
The Module interface provides a no-op default:
default void onConfigReload(ConfigurationNode moduleConfig) {}Override it whenever your module has runtime state derived from config.
Minimal pattern
@Overridepublic void onConfigReload(ConfigurationNode moduleNode) { try { this.config = moduleNode.get(MyConfig.class); } catch (SerializationException e) { throw new ModuleException("failed to reload config for module " + id(), e); }}This is sufficient when no listener or background task holds a direct reference to config — you can just read this.config from the main thread each time.
Thread-safe propagation with AtomicReference
When a listener or async task needs live access to the current config, hold it in an AtomicReference and pass a Supplier lambda to the listener. AntiCheatModule and ChatFormatModule both use this pattern.
// In the module class:private final AtomicReference<MyConfig> configRef = new AtomicReference<>();
@Overridepublic void enable(ModuleContext ctx) { try { configRef.set(ctx.moduleConfig().get(MyConfig.class)); } catch (SerializationException e) { throw new ModuleException("failed to load config for module " + id(), e); } // Pass a Supplier so the listener always reads the current value: ctx.events().register(new MyListener(configRef::get, ...));}
@Overridepublic void onConfigReload(ConfigurationNode moduleNode) { try { configRef.set(moduleNode.get(MyConfig.class)); } catch (SerializationException e) { throw new ModuleException("failed to reload config for module " + id(), e); }}// In the listener:public final class MyListener implements Listener { private final Supplier<MyConfig> config;
public MyListener(Supplier<MyConfig> config, ...) { this.config = config; }
@EventHandler public void onSomeEvent(SomeEvent e) { MyConfig cfg = config.get(); // always the latest value ... }}AtomicReference.set is a single volatile write — safe to call from the main thread while Bukkit event handlers read it concurrently (configRef::get).
5. Error handling contract
- If
onConfigReloadthrows aRuntimeException(includingModuleException),ModuleRegistrycatches it, logs it atWARNING, and proceeds with the next module. - Config deserialization errors should be wrapped in
ModuleExceptionso the log message identifies the module. - After a failed reload the module retains whatever config it held before the call.