Chunk Saving
The server continuously saves modified chunks to disk. Chunks are not saved all at once — the save system scans for dirty chunks every 0.5 seconds and flushes them in small batches each tick. For the on-disk format, see Storage Format.
Dirty Flags
Section titled “Dirty Flags”A chunk is saved when any of its components are marked dirty. Each component tracks its own dirty state, and the chunk is considered dirty if any component needs saving:
| Component | Triggers |
|---|---|
| BlockChunk | Block placed/removed, heightmap changed, tint changed, environment changed, ticking state changed |
| BlockComponentChunk | Block state modified (instance blocks, teleport config, etc.) |
| EntityChunk | Entity added or removed from chunk |
| WorldChunk | Newly generated chunk (if saveNewChunks config is enabled) |
Other systems also mark chunks dirty: fluid simulation, farming ticks, lighting recalculation, entity movement between chunks, and respawn point changes.
Mod-registered ChunkStore components with a codec are serialized alongside built-in components when a chunk saves. Entity components registered on the EntityStore are saved as part of the EntityChunk.
Manual Marking
Section titled “Manual Marking”WorldChunk worldChunk = store.getComponent(ref, WorldChunk.getComponentType());worldChunk.markNeedsSaving();Or via command: /chunk marksave <x> <z>
Automatic Saving
Section titled “Automatic Saving”The save system runs as a ticking system on each world:
- Every 0.5s: scans all chunks for
getNeedsSaving() && !isSaving() - Every tick: dequeues up to
ForkJoinPool.commonPool().getParallelism()chunks (roughly CPU cores - 1) and saves them asynchronously - On completion: sets
ChunkFlag.ON_DISK, clears the dirty flag, marksisSaving = false
A chunk being saved (isSaving = true) is not re-queued until the current save completes.
World Config Saving
Section titled “World Config Saving”World config (config.json) is saved separately by WorldConfigSaveSystem, which runs every 10 seconds if the config has changed.
Manual Saves
Section titled “Manual Saves”Force Save All Chunks
Section titled “Force Save All Chunks”// async - saves all dirty chunks in the worldCompletableFuture<Void> future = ChunkSavingSystems.saveChunksInWorld(store);This iterates all chunks, queues every dirty one, then drains the entire queue synchronously (not batched). Used internally by the /world save command and on shutdown.
Commands
Section titled “Commands”| Command | Description |
|---|---|
/world save <world> | Save all dirty chunks + world config for a specific world |
/world save --all | Save all worlds |
/chunk marksave <x> <z> | Mark a specific chunk as needing save (picked up by next scan) |
/world settings chunksaving set <true|false> | Toggle chunk saving at runtime |
See World Commands and Chunk Commands for details.
Save Configuration
Section titled “Save Configuration”Two world config options control saving behavior:
| Config | Default | Description |
|---|---|---|
canSaveChunks | true | Master toggle — when false, no chunks are saved (including on shutdown) |
saveNewChunks | true | Whether newly generated chunks are marked dirty. Disable to avoid saving chunks generated by exploration, so worldgen changes apply on next visit |
WorldConfig config = world.getWorldConfig();
// disable all chunk savingconfig.setCanSaveChunks(false);
// prevent newly generated chunks from being savedconfig.setSaveNewChunks(false);Setting saveNewChunks to false is useful during worldgen development — chunks generated by players exploring won’t be persisted, so regeneration picks up changes on the next visit.
ChunkSaveEvent
Section titled “ChunkSaveEvent”ChunkSaveEvent fires for each chunk about to be saved. It is cancellable — cancelled chunks are skipped.
This is an ECS event dispatched on the ChunkStore, not a regular event bus event. To listen, create an EntityEventSystem:
public class ChunkSaveHandler extends EntityEventSystem<ChunkStore, ChunkSaveEvent> { private final MyPlugin plugin;
public ChunkSaveHandler(MyPlugin plugin) { super(ChunkSaveEvent.class); this.plugin = plugin; }
@Override public void handle(int index, ArchetypeChunk<ChunkStore> archetypeChunk, Store<ChunkStore> store, CommandBuffer<ChunkStore> commandBuffer, ChunkSaveEvent event) { WorldChunk chunk = event.getChunk();
// prevent saving a specific chunk if (shouldSkip(chunk)) { event.setCancelled(true); return; }
// track saved chunks for delta map rendering, etc. long chunkIndex = ChunkUtil.indexChunk(chunk.getX(), chunk.getZ()); plugin.onChunkSaved(chunkIndex); }
@Override public Query<ChunkStore> getQuery() { return WorldChunk.getComponentType(); // run on all chunk entities }}Register the handler in setup():
this.getChunkStoreRegistry().registerSystem(new ChunkSaveHandler(this));The event fires continuously as dirty chunks are flushed — not in one large batch. For using this event with map tile invalidation, see Delta Rendering.
Chunk Flags
Section titled “Chunk Flags”Each WorldChunk has lifecycle flags accessible via worldChunk.is(ChunkFlag):
| Flag | Description |
|---|---|
START_INIT | Chunk initialization has started |
INIT | Chunk is fully initialized |
NEWLY_GENERATED | Chunk was generated (not loaded from disk) |
ON_DISK | Chunk has been saved to disk at least once |
TICKING | Chunk is currently ticking |
// check if a chunk has ever been savedboolean saved = worldChunk.is(ChunkFlag.ON_DISK);
// check if chunk was loaded from disk vs freshly generatedboolean fromDisk = !worldChunk.is(ChunkFlag.NEWLY_GENERATED);Shutdown Behavior
Section titled “Shutdown Behavior”On world shutdown, the server synchronously saves all remaining dirty chunks before tearing down the world. If canSaveChunks is false, chunks are not saved and a warning is logged.
Backup System
Section titled “Backup System”The server supports automated backups via CLI flags:
| Flag | Description |
|---|---|
--backup | Enable backups |
--backup-frequency <minutes> | Backup interval (minimum 1 minute) |
--backup-dir <path> | Backup output directory |
--backup-max-count <n> | Maximum backups to keep (default 5) |
During a backup:
- Chunk saving is paused on all worlds
- In-flight saves are waited on
- The universe directory is zipped to the backup directory
- Chunk saving resumes
Old backups beyond maxCount are archived every 12 hours, then deleted.