Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Level data and rules

Verified against Minecraft 26.3 · Reference · Who owns the seed, the spawn, the rules, the border and the dimensions, where each is saved, and what tells the client — looked up, not watched.

The facts about a world that are not blocks or entities: its seed and dimensions, its spawn, its game time, its difficulty, its rules, its border, its scoreboard and maps. In 26.3 about half of them have left level.dat — PrimaryLevelData is a stub and everything else is a SavedData file under a data/ folder, one server-global and one per dimension — so the question this page answers is always the same one: which file remembers this, and who is allowed to change it. The table under who owns what is the page. Six sections follow it, one for each datum whose answer is longer than a row — what is left in level.dat, with how a save happens inside it, the two saved-data storages, the rules, the border, the dimensions and the seed, and difficulty and weather — and a row with no section below it is a row the table has already answered in full.

Five parts point here — III for what a boot reads, IV for what the world is made of, VIII for the two game rules that decide whether the server checks your movement, IX for the rule values a server sends a gamemaster’s client on request, and XII for the seed — and the level tick is where most of them are read.

Who owns what

In the saved as column, dimensions/…/ stands for dimensions/<namespace>/<path>/, the folder a dimension’s own id names; everything else is relative to the world folder.

datumownersaved astold to the client by
seed, structures, bonus chest, dimension listWorldGenSettings (MinecraftServer.getWorldGenSettings)data/minecraft/world_gen_settings.datthe obfuscated seed in CommonPlayerSpawnInfo, the dimension list in ClientboundLoginPacket; structures and the bonus chest, nothing
world spawnPrimaryLevelData.respawnDatalevel.datClientboundSetDefaultSpawnPositionPacket — the effective spawn below on join, respawn and dimension change, the stored one when it is set
game timePrimaryLevelData.gameTime (shared by every level)level.datClientboundSetTimePacket
day timeServerClockManagerdata/minecraft/world_clocks.datClientboundSetTimePacket
difficulty, lock, hardcoreLevelSettings.DifficultySettingslevel.datClientboundChangeDifficultyPacket — hardcore alone rides in ClientboundLoginPacket
game type, allow-commands, name, data packsLevelSettingslevel.datthe game type only as a new player’s default, through CommonPlayerSpawnInfo; the enabled features at the configuration phase; the name and allow-commands, nothing
game rulesGameRuleMap via GameRulesdata/minecraft/game_rules.datfive rules only, plus ClientboundGameRuleValuesPacket on request
weatherWeatherDatadata/minecraft/weather.datClientboundGameEventPacket
world borderWorldBorder, one per ServerLeveldimensions/…/data/minecraft/world_border.datClientboundInitializeBorderPacket and the five ClientboundSetBorder… packets
scoreboardServerScoreboard (MinecraftServer.scoreboard), buffered by ScoreboardSaveData at save timedata/minecraft/scoreboard.datClientboundSetObjectivePacket, ClientboundSetScorePacket, ClientboundSetPlayerTeamPacket
mapsMapItemSavedData per MapId, MapIndex for the counterdata/minecraft/maps/<n>.dat, data/minecraft/maps/last_id.datClientboundMapItemDataPacket
raidsRaids per leveldimensions/…/data/minecraft/raids.datboss bars
chunk ticketsTicketStorage per level (tickets)dimensions/…/data/minecraft/chunk_tickets.dat—
dragon fightEnderDragonFight, where DimensionType.hasEnderDragonFightdimensions/…/data/minecraft/ender_dragon_fight.datboss bars
boss bars, scheduled functions, random sequences, stopwatches, trader timers, command storageCustomBossEvents, TimerQueue, RandomSequences, Stopwatches, WanderingTraderData, CommandStoragedata/minecraft/<id>.dat, and one data/<namespace>/command_storage.dat per namespaceboss bars only
player dataPlayerDataStorageplayers/data/<uuid>.dat—

What is left in level.dat

LevelData is the read-only interface every Level exposes, and it is small: LevelData.getRespawnData, LevelData.getGameTime, LevelData.isHardcore, LevelData.getDifficulty and LevelData.isDifficultyLocked. No day time — that has left level data entirely for ServerClockManager (who owns the clock) — no weather, no rules, no border. LevelData.RespawnData is the world spawn as a GlobalPos with yaw and pitch — it carries a dimension, so /setworldspawn can point anywhere. WritableLevelData adds WritableLevelData.setSpawn; ServerLevelData adds eight more — the level name, the game type and its setter, ServerLevelData.isInitialized and its setter, ServerLevelData.isAllowCommands and its setter, and ServerLevelData.setGameTime; WorldData is the server-wide view (WorldData.getLevelSettings, WorldData.getDataConfiguration, WorldData.enabledFeatures, WorldData.wasModded, WorldData.getKnownServerBrands, WorldData.overworldData).

PrimaryLevelData implements all of them — the overworld’s level data and the whole server’s — with twelve fields: PrimaryLevelData.settings (a LevelSettings: name, GameType, a LevelSettings.DifficultySettings of difficulty, hardcore and lock, allow-commands, WorldDataConfiguration), PrimaryLevelData.respawnData, PrimaryLevelData.gameTime, PrimaryLevelData.initialized, PrimaryLevelData.knownServerBrands, PrimaryLevelData.wasModded, PrimaryLevelData.removedFeatureFlags, PrimaryLevelData.singlePlayerUUID, PrimaryLevelData.version, PrimaryLevelData.specialWorldProperty (PrimaryLevelData.SpecialWorldProperty.FLAT / PrimaryLevelData.SpecialWorldProperty.DEBUG / none), PrimaryLevelData.versionHistory (every data version that has saved the world, written as version_history), and PrimaryLevelData.worldGenSettingsLifecycle, the DFU Lifecycle that makes the world-select screen warn about experimental settings. That is the whole stub: seed, dimensions, rules, border, weather, dragon fight, boss bars, scheduled events and trader timers are all SavedData files. PrimaryLevelData.createTag builds the payload — flat, no wrapper — and it is LevelStorageSource.LevelStorageAccess.saveDataTag that nests it under Data; PrimaryLevelData.parse reads it back.

DerivedLevelData is what every other ServerLevel gets, built in MinecraftServer.createLevels: spawn, game time and initialised state forwarded to the overworld’s data, and everything the whole server shares — name, game type, hardcore, allow-commands, difficulty and its lock — read straight off the WorldData. Only DerivedLevelData.setSpawn writes anything; DerivedLevelData.setGameTime, DerivedLevelData.setGameType, DerivedLevelData.setAllowCommands and DerivedLevelData.setInitialized are no-ops. That forwarding is how game time comes to be shared by every level.

The file is written by LevelStorageSource.LevelStorageAccess.saveDataTag → LevelStorageSource.LevelStorageAccess.saveLevelData: a temp file, then Util.safeReplaceFile renames the old level.dat to level.dat_old and the temp into place, ten retries per step with a rollback.

One method saves all three

Nothing in the table above saves itself, and but for player data MinecraftServer.saveAllChunks is the single call behind every row. It does four things in order: it flushes the live ServerScoreboard into its ScoreboardSaveData (which is what the scoreboard row means by buffered at save time — the scoreboard is a live object all tick and becomes saved data only here), saves every level’s chunks, writes level.dat through LevelStorageSource.LevelStorageAccess.saveDataTag, and then either joins the saved-data storage or schedules it. So the rows with no section below them — day time, the scoreboard, maps, raids, chunk tickets, the dragon fight and the boss-bar row’s six owners — are saved by being SavedData, and that one flush is the only special case among them. Player data is the other road: PlayerDataStorage is not SavedData, and PlayerList.save writes a player’s file, both from PlayerList.saveAll and whenever the player leaves. MinecraftServer.saveEverything wraps MinecraftServer.saveAllChunks with PlayerList.saveAll in front, and that is the pair autosave and /save-all reach; shutdown calls the two itself.

LevelSummary — the world-select row — is read without opening the world at all, by LevelStorageSource.readLevelSummary, and it is where the numbers on that screen come from: a LevelVersion (LevelVersion.levelDataVersion, LevelVersion.lastPlayed, LevelVersion.minecraftVersionName, LevelVersion.minecraftVersion — the DataVersion the needs upgrading and from a newer version warnings compare — and LevelVersion.snapshot) plus the LevelSettings. Two subclasses stand for worlds it could not read: LevelSummary.CorruptedLevelSummary, and LevelSummary.SymlinkLevelSummary for a world whose level.dat is a link to a place the allow list refuses.

Every path a world folder has a name for

LevelResource names thirteen paths under a world folder, and the per-player files are three separate ones rather than one player data row:

LevelResourcethe pathwhat is in it
LevelResource.LEVEL_DATA_FILElevel.datwhat this page’s table calls level data
LevelResource.OLD_LEVEL_DATA_FILElevel.dat_oldthe previous one, kept by the rename dance above
LevelResource.LOCK_FILEsession.lockheld for as long as the world is open (starting a server)
LevelResource.DATAdata/the server-global SavedData files: the scoreboard, the game rules, the world-gen settings, the boss bars
LevelResource.PLAYER_DATA_DIRplayers/data/one .dat per player, read twice on join (players and sessions)
LevelResource.PLAYER_ADVANCEMENTS_DIRplayers/advancements/one JSON per player, the progress half of every advancement (advancements)
LevelResource.PLAYER_STATS_DIRplayers/stats/one JSON per player, and every statistic is also a scoreboard criterion (scores, teams and stored data)
LevelResource.PLAYER_OLD_DATA_DIRplayers/the parent of the three, and where the oldest worlds’ name-keyed player files sit until a dedicated server’s boot moves them, through OldUsersConverter, into players/data/
LevelResource.ROOT · LevelResource.ICON_FILE · LevelResource.GENERATED_DIR · LevelResource.DATAPACK_DIR · LevelResource.MAP_RESOURCE_FILE. , icon.png, generated/, datapacks/, resourcepacks/resources.zipthe world folder itself, its world-select thumbnail, what a structure block saves (jigsaw and templates), the world’s own packs, and the world’s own resource pack, which the client loads when it opens the world

Thirteen constants, and nothing else in a world folder has a name here: the dimensions/ tree is named by DimensionType.getStorageFolder, and the region files inside it by ChunkPos arithmetic (chunk storage).

The spawn every level reports is the server’s, not each level’s

ServerLevel.getRespawnData forwards to MinecraftServer.getRespawnData, which returns MinecraftServer.effectiveRespawnData — recomputed by MinecraftServer.updateEffectiveRespawnData through Level.getWorldBorderAdjustedRespawnData, which relocates a spawn that has fallen outside the border to the border centre’s surface, and by MinecraftServer.findRespawnDimension, which falls back to the overworld when the stored dimension no longer exists. So every level reports the same spawn, and it need not be the one level.dat holds: it is the stored one wherever that is inside the border it is tested against — the overworld’s, when the stored dimension no longer exists — and a relocated one where it is not.

Two saved-data storages, neither of them the overworld’s

MinecraftServer.savedDataStorage (MinecraftServer.getDataStorage) is server-global at <world>/data/; maps, scoreboard, rules, weather and clocks are server-wide. ServerChunkCache.savedDataStorage (ServerChunkCache.getDataStorage, forwarded by ServerLevel.getDataStorage) is per dimension at dimensions/<namespace>/<path>/data/ — the overworld included (chunk storage); raids, tickets, the border and the dragon fight are per dimension. The overworld’s per-dimension files sit under dimensions/ like everyone else’s, so neither storage is “the overworld’s”: one is the server’s and one is each dimension’s.

What a saved-data file is

SavedData is one flag, SavedData.dirty (SavedData.setDirty); a SavedDataType is an id, a constructor, a Codec and a DataFixTypes. SavedDataStorage caches them per folder (SavedDataStorage.computeIfAbsent, SavedDataStorage.get, SavedDataStorage.set) and writes each as { data, DataVersion }, gzip-compressed. How a dirty entry reaches the disk — encoded straight from the live object on the caller’s thread, with no copy first, written on the IO pool, and joined by a flush save, shutdown or the world upgrader — is half the bargain a chunk makes, and chunk storage is where that bargain is explained.

The id is an Identifier, so every saved-data file lives under a namespace folder — the path is data/<namespace>/<path>.dat — and vanilla’s are all under data/minecraft/. Command storage is the one place the namespace is not minecraft: each gets its own data/<namespace>/command_storage.dat.

Game rules are a registry

GameRule is a registry entry in Registries.GAME_RULE / BuiltInRegistries.GAME_RULE, bootstrapped by GameRules.bootstrap: GameRule.category (a GameRuleCategory — GameRuleCategory.PLAYER, GameRuleCategory.MOBS, GameRuleCategory.SPAWNING, GameRuleCategory.DROPS, GameRuleCategory.UPDATES, GameRuleCategory.CHAT, GameRuleCategory.MISC), GameRule.gameRuleType (GameRuleType.INT or GameRuleType.BOOL), GameRule.argument (a Brigadier type), GameRule.valueCodec, GameRule.defaultValue and GameRule.requiredFeatures. Ids are snake_case and namespaceable — GameRules.ADVANCE_TIME, GameRules.SPAWN_MOBS, GameRules.SPAWN_MONSTERS, GameRules.KEEP_INVENTORY, GameRules.RANDOM_TICK_SPEED, GameRules.PLAYERS_SLEEPING_PERCENTAGE, GameRules.RESPAWN_RADIUS, GameRules.MAX_ENTITY_CRAMMING, GameRules.MAX_SNOW_ACCUMULATION_HEIGHT, GameRules.MAX_MINECART_SPEED (feature-gated) … fifty-nine of them, with their defaults, in the generated table; the old names and the nested value classes have rows in naming drift.

The values are saved data, not level data: a GameRuleMap — SavedData, game_rules.dat, server-global — wrapped by the GameRules instance in MinecraftServer.gameRules. ServerLevel.getGameRules returns the server’s: one set for every dimension, and Level has no rules accessor at all, so no ClientLevel can read a rule — the client’s GameRules objects belong to the two AbstractGameRulesScreens, WorldCreationGameRulesScreen and InWorldGameRulesScreen, and to WorldCreationUiState, whose rules become the new world’s when it is created. The accessors are GameRules.get, GameRules.set (which calls MinecraftServer.onGameRuleChanged) and GameRules.visitGameRuleTypes (how GameRuleCommand.register builds two literals per rule — the bare id and the namespaced one).

What the client hears

Five rules are pushed, and the rest reach a client only when a gamemaster asks — as data through the editor below, or as the chat feedback of /gamerule. All three of GameRules.REDUCED_DEBUG_INFO, GameRules.LIMITED_CRAFTING and GameRules.IMMEDIATE_RESPAWN ride in ClientboundLoginPacket at join (the last inverted, as showDeathScreen); a change afterwards goes as a ClientboundEntityEventPacket for the first and a ClientboundGameEventPacket for the other two (ClientboundGameEventPacket.LIMITED_CRAFTING, ClientboundGameEventPacket.IMMEDIATE_RESPAWN); GameRules.LOCATOR_BAR through ServerWaypointManager; and GameRules.ADVANCE_TIME, which broadcasts a full clock sync because a paused clock is expressed on the wire as rate 0 (environment attributes). One mechanism in this area sends the client nothing at all: MinecraftServer.updateMobSpawningFlags, which pushes a flag down the server’s own chain instead (below).

There is an in-game editor: ServerboundClientCommandPacket.Action.REQUEST_GAMERULE_VALUES → ServerGamePacketListenerImpl.sendGameRuleValues → ClientboundGameRuleValuesPacket → InWorldGameRulesScreen, and edits back as ServerboundSetGameRulePacket → ServerGamePacketListenerImpl.handleSetGameRule (gated on Permissions.COMMANDS_GAMEMASTER).

The border is per dimension

The border has no lecture. It is the one mechanism in Part IV’s packages whose home is this page: it sits on no conveyor, it belongs to none of the four side-systems, and what a reader needs of it is a set of numbers, a pair of extents and a list of packets — which is what a Reference page is for. Part IV’s landing page declares it as such.

WorldBorder is SavedData — world_border.dat, per dimension, fetched by ServerLevel.getWorldBorder through the cache on every call. Nothing scales the Nether’s border by DimensionType.coordinateScale — each dimension’s file has its own values, so the Nether has the same numbers unless someone sets them.

WorldBorder.settings (WorldBorder.Settings: centre, damage per block, safe zone, warning blocks and time, size, lerp time and target; WorldBorder.Settings.DEFAULT is 0,0 / 0.2 / 5 / 5 / 300 / WorldBorder.MAX_SIZE) is the loaded snapshot, never written again; WorldBorder.applyInitialSettings pushes it into the live fields once, resuming a lerp in progress from where the save left it, and saving reads the live fields back out. The live defaults are not the persisted ones — a fresh WorldBorder starts with a warning time of 15, not 300.

The live extent is a WorldBorder.BorderExtent — WorldBorder.StaticBorderExtent or WorldBorder.MovingBorderExtent, which WorldBorder.tick advances (the level tick). A moving border marks itself for saving every tick: WorldBorder.MovingBorderExtent marks the saved data dirty on every advance, and a stationary one never does. WorldBorder.MAX_SIZE is 59,999,968; MinecraftServer.getAbsoluteMaxWorldSize is applied to every level’s border in MinecraftServer.createLevels — 29,999,984, which is MinecraftServer’s own answer and so the integrated server’s, while DedicatedServer overrides it with max-world-size. WorldBorder.isWithinBounds, WorldBorder.clampToBounds, WorldBorder.getDistanceToBorder, WorldBorder.getCollisionShape are the readers; BorderStatus colours the client’s wall.

For sync, PlayerList.addWorldborderListener registers a BorderChangeListener per level that broadcasts, dimension-scoped, ClientboundSetBorderSizePacket, ClientboundSetBorderLerpSizePacket, ClientboundSetBorderCenterPacket, ClientboundSetBorderWarningDelayPacket and ClientboundSetBorderWarningDistancePacket; PlayerList.sendLevelInfo sends ClientboundInitializeBorderPacket on join and dimension change. ClientLevel.worldBorder is a plain WorldBorder ticked in ClientLevel.tick.

Dimensions and the seed

DimensionType is a record of sixteen components, and they fall into four kinds:

kindcomponents
the shape of the spaceDimensionType.minY, DimensionType.height, DimensionType.logicalHeight, DimensionType.coordinateScalehow tall, how deep, how high a portal or a chorus fruit may reach, and what a coordinate becomes when you step through a portal
what the sky doesDimensionType.hasSkyLight, DimensionType.hasCeiling, DimensionType.hasFixedTime, DimensionType.ambientLight, DimensionType.skybox (a DimensionType.Skybox), DimensionType.cardinalLightTypethe six that decide whether there is daylight, a roof over it, whether it ever counts as day or night, and what is drawn where the blocks stop
what may live hereDimensionType.monsterSettings, DimensionType.infiniburn, DimensionType.hasEnderDragonFightthe two monster-spawn light tests (DimensionType.MonsterSettings.monsterSpawnLightTest and DimensionType.MonsterSettings.monsterSpawnBlockLightLimit), the block tag that burns for ever, and the dragon fight as a flag rather than hard-wired to Level.END
the three that are a system of their ownDimensionType.attributes (an EnvironmentAttributeMap), DimensionType.timelines, DimensionType.defaultClock (a WorldClock holder; WorldClocks.OVERWORLD, WorldClocks.THE_END)where the gameplay booleans a 1.21-era reader will look for on this record have gone (the stack a value falls through); naming drift has the row for each

DimensionType.getStorageFolder names the on-disk folder. Defaults are DimensionDefaults (DimensionDefaults.OVERWORLD_MIN_Y −64, DimensionDefaults.OVERWORLD_LEVEL_HEIGHT 384, DimensionDefaults.NETHER_LOGICAL_HEIGHT 128); the built-in keys are BuiltinDimensionTypes.OVERWORLD, BuiltinDimensionTypes.NETHER, BuiltinDimensionTypes.END, BuiltinDimensionTypes.OVERWORLD_CAVES.

LevelStem is a DimensionType holder plus a ChunkGenerator (LevelStem.OVERWORLD, LevelStem.NETHER, LevelStem.END). Registries.LEVEL_STEM and Registries.DIMENSION share the id dimension (identifiers and registries); Level.OVERWORLD, Level.NETHER, Level.END are ResourceKeys under the latter, and Level.dimension / Level.dimensionType are the accessors. Level.canHaveWeather is sky light, no ceiling, not the End.

The seed and the dimension list are WorldGenSettings — SavedData, world_gen_settings.dat, server-global, and written not at world creation but by the server’s first save, the MinecraftServer constructor having pushed it into the storage, as it does on every boot (creating a world is where it comes from) — holding WorldOptions (WorldOptions.seed, WorldOptions.generateStructures, WorldOptions.generateBonusChest) and WorldDimensions (the stem map; WorldDimensions.bake produces the registry). It is read before the server exists by LevelStorageSource.getLevelDataAndDimensions and pushed in with SavedDataStorage.set; if the file is missing or unreadable the loader logs an error and substitutes a whole default WorldGenSettings — a random seed, structures on, no bonus chest, and the data packs’ dimension list in place of the saved one — then carries on loading the world. MinecraftServer.createLevels makes one ServerLevel per stem; MinecraftServer.levelKeys go out in ClientboundLoginPacket, the dimension type via registry sync, and the biome-zoom-obfuscated seed in CommonPlayerSpawnInfo.

Difficulty and weather

Difficulty (Difficulty.PEACEFUL … Difficulty.HARD) sits in LevelSettings.DifficultySettings. MinecraftServer.setDifficulty writes it, then calls MinecraftServer.updateMobSpawningFlags and MinecraftServer.sendDifficultyUpdate → ClientboundChangeDifficultyPacket.

Peaceful does not turn the spawner off. The flag MinecraftServer.updateMobSpawningFlags pushes down — Level.setSpawnSettings, forwarded to ServerChunkCache.setSpawnSettings, one boolean per level — is ServerLevel.isSpawningMonsters, and that reads two game rules, GameRules.SPAWN_MOBS and GameRules.SPAWN_MONSTERS, and never the difficulty. Difficulty is spent on the mobs themselves instead: EntityType.isAllowedInPeaceful gates each spawn attempt, and Mob.checkDespawn discards a mob that is not allowed in peaceful on its next despawn check. So the two paths are independent — GameRules.SPAWN_MOBS empties the spawner and GameRules.SPAWN_MONSTERS its hostile half, while peaceful empties the world — and MinecraftServer.setDifficulty calls MinecraftServer.updateMobSpawningFlags only because a rule may have changed under it since the last call. DedicatedServer.forceDifficulty applies server.properties at boot with the lock ignored; there is no getForcedDifficulty. DifficultyInstance — local difficulty — is built by ServerLevel.getCurrentDifficultyAt from ChunkAccess.getInhabitedTime, Level.getOverworldClockTime and the moon phase (an environment attribute, EnvironmentAttributes.MOON_PHASE, indexed into DimensionType.MOON_BRIGHTNESS_PER_PHASE — the one piece of the old moon logic still on DimensionType; see environment attributes). What reads it is Part VI: every Mob.finalizeSpawn is handed one (entity lifecycle), which is why a zombie that can break doors is a roll made once, at spawn, in the chunk you have been standing in (AI).

WeatherData — server-global SavedData, MinecraftServer.getWeatherData — was covered in the level tick. PrimaryLevelData stores no rain fields.

Where to look

PrimaryLevelData.setTagData · DerivedLevelData · LevelSettings · LevelStorageSource.getLevelDataAndDimensions · LevelStorageSource.LevelStorageAccess.saveDataTag · SavedDataStorage.scheduleSave · SavedDataType · MinecraftServer.createLevels · MinecraftServer.getDataStorage · ServerChunkCache.getDataStorage · GameRules.bootstrap · GameRules.set · MinecraftServer.onGameRuleChanged · GameRuleMap · GameRuleCommand.register · WorldBorder.applyInitialSettings · WorldBorder.tick · PlayerList.addWorldborderListener · DimensionType · LevelStem · WorldGenSettings · WorldDimensions.bake · MinecraftServer.setDifficulty · ServerLevel.getCurrentDifficultyAt


Rules: names, never code · how the system works, not how the code reads · newest version only · every backticked name passes tools/verify_names.py.