Introduction
Verified against Minecraft 26.2
This is documentation of how Java Minecraft works — its systems, the data they own, when they run, and how they talk to each other — for the current version of the game and no other. It is the written half of a lecture series; each chapter is one lecture’s notes and follows one scenario through the codebase.
Three things to know before reading:
- Names, never code. Chapters name classes, methods and packages so that anyone with the decompiled source can find them in a minute. They do not reproduce the source. If you need the code, decompile the game.
- Mojang’s names. The identifiers here are Mojang’s official mappings, which the decompiled source uses. Fabric’s Yarn mappings differ; where a modder would not recognise a class under its official name, the Yarn name is noted once.
- Verified. Every backticked identifier in every chapter is checked against the stated version’s decompile before publishing. When a new version lands, chapters are re-verified and the header updated; there are no version-difference sections.
Maps
Generated views of the 26.2 decompile, regenerated each version with
python tools/map_source.py. They are the “where is everything” answer a
newcomer wants before any system page makes sense.
- Packages — classes and lines per package, and how many are client-only.
- Biggest classes — where the mass is.
- Hierarchy — the widest inheritance trees.
- Fan-in — the most-imported classes: the hubs.
Packages
packages — 26.2
| package (depth 3) | classes | client-only | lines |
|---|---|---|---|
net/minecraft/world | 2585 | 0 | 304,897 |
net/minecraft/client | 1864 | 1864 | 172,711 |
net/minecraft/util | 717 | 0 | 53,275 |
net/minecraft/server | 420 | 0 | 52,720 |
com/mojang/blaze3d | 211 | 211 | 26,111 |
net/minecraft/network | 411 | 0 | 23,378 |
net/minecraft/data | 163 | 0 | 15,587 |
com/mojang/realmsclient | 127 | 127 | 13,217 |
net/minecraft/commands | 122 | 0 | 13,001 |
net/minecraft/core | 110 | 0 | 11,239 |
net/minecraft/nbt | 43 | 0 | 8,001 |
net/minecraft/advancements | 116 | 0 | 7,735 |
net/minecraft/gametest | 47 | 0 | 5,514 |
net/minecraft/sounds | 6 | 0 | 2,101 |
net/minecraft/tags | 29 | 0 | 1,788 |
net/minecraft/resources | 15 | 0 | 1,764 |
net/minecraft | 19 | 0 | 1,665 |
net/minecraft/references | 5 | 0 | 1,434 |
com/mojang/math | 10 | 0 | 922 |
net/minecraft/stats | 10 | 0 | 873 |
net/minecraft/gizmos | 15 | 0 | 569 |
net/minecraft/recipebook | 3 | 0 | 350 |
net/minecraft/locale | 3 | 0 | 247 |
net/minecraft/realms | 4 | 4 | 203 |
| total | 7055 | 2206 | 719,302 |
| package (depth 4) | classes | client-only | lines |
|---|---|---|---|
net/minecraft/world/level | 1312 | 0 | 146,417 |
net/minecraft/world/entity | 716 | 0 | 109,061 |
net/minecraft/client/gui | 444 | 444 | 59,057 |
net/minecraft/client/renderer | 701 | 701 | 48,412 |
net/minecraft/world/item | 314 | 0 | 29,281 |
net/minecraft/util/datafix | 396 | 0 | 26,372 |
net/minecraft/network/protocol | 293 | 0 | 12,934 |
net/minecraft/server/commands | 102 | 0 | 12,781 |
net/minecraft/client/model | 267 | 267 | 12,696 |
net/minecraft/util | 91 | 0 | 12,067 |
net/minecraft/server/level | 42 | 0 | 11,977 |
net/minecraft/client/multiplayer | 63 | 63 | 11,169 |
net/minecraft/client | 41 | 41 | 10,709 |
net/minecraft/commands/arguments | 71 | 0 | 8,847 |
net/minecraft/client/resources | 101 | 101 | 7,612 |
net/minecraft/nbt | 36 | 0 | 7,489 |
com/mojang/blaze3d/vulkan | 40 | 40 | 7,477 |
net/minecraft/world/inventory | 64 | 0 | 7,082 |
net/minecraft/client/particle | 87 | 87 | 6,806 |
net/minecraft/client/data | 28 | 28 | 6,176 |
com/mojang/realmsclient/gui | 40 | 40 | 5,830 |
com/mojang/blaze3d/opengl | 28 | 28 | 5,627 |
net/minecraft/gametest/framework | 45 | 0 | 5,495 |
net/minecraft/core | 38 | 0 | 5,493 |
net/minecraft/server/network | 28 | 0 | 5,409 |
net/minecraft/data/worldgen | 56 | 0 | 5,369 |
net/minecraft/server | 27 | 0 | 5,227 |
net/minecraft/server/packs | 55 | 0 | 4,975 |
net/minecraft/network/chat | 63 | 0 | 4,818 |
net/minecraft/util/profiling | 70 | 0 | 4,260 |
net/minecraft/server/jsonrpc | 65 | 0 | 4,094 |
com/mojang/blaze3d/platform | 29 | 29 | 3,896 |
net/minecraft/network | 42 | 0 | 3,732 |
net/minecraft/util/filefix | 57 | 0 | 3,544 |
net/minecraft/advancements/predicates | 54 | 0 | 3,459 |
net/minecraft/world/phys | 28 | 0 | 3,020 |
net/minecraft/data/loot | 21 | 0 | 2,883 |
net/minecraft/advancements/triggers | 49 | 0 | 2,809 |
net/minecraft/server/players | 19 | 0 | 2,766 |
net/minecraft/core/component | 30 | 0 | 2,668 |
com/mojang/realmsclient/client | 19 | 19 | 2,534 |
com/mojang/blaze3d/vertex | 16 | 16 | 2,420 |
com/mojang/blaze3d/systems | 26 | 26 | 2,308 |
net/minecraft/data/recipes | 17 | 0 | 2,227 |
net/minecraft/client/sounds | 17 | 17 | 2,152 |
net/minecraft/sounds | 6 | 0 | 2,101 |
net/minecraft/util/parsing | 29 | 0 | 1,879 |
net/minecraft/commands | 14 | 0 | 1,873 |
com/mojang/realmsclient/dto | 39 | 39 | 1,866 |
net/minecraft/client/player | 9 | 9 | 1,821 |
net/minecraft/server/dedicated | 7 | 0 | 1,809 |
net/minecraft/tags | 29 | 0 | 1,788 |
net/minecraft/resources | 15 | 0 | 1,764 |
net/minecraft/world/attribute | 28 | 0 | 1,705 |
net/minecraft | 19 | 0 | 1,665 |
net/minecraft/world | 25 | 0 | 1,664 |
net/minecraft/data/tags | 29 | 0 | 1,660 |
com/mojang/realmsclient | 4 | 4 | 1,524 |
net/minecraft/advancements | 13 | 0 | 1,467 |
net/minecraft/world/scores | 16 | 0 | 1,442 |
net/minecraft/references | 5 | 0 | 1,434 |
com/mojang/realmsclient/util | 20 | 20 | 1,399 |
net/minecraft/network/codec | 7 | 0 | 1,372 |
net/minecraft/util/debug | 19 | 0 | 1,370 |
net/minecraft/world/effect | 20 | 0 | 1,306 |
net/minecraft/data | 10 | 0 | 1,264 |
net/minecraft/client/telemetry | 18 | 18 | 1,221 |
net/minecraft/core/dispenser | 14 | 0 | 1,094 |
com/mojang/blaze3d/audio | 12 | 12 | 1,013 |
com/mojang/blaze3d/pipeline | 11 | 11 | 954 |
net/minecraft/util/worldupdate | 6 | 0 | 937 |
net/minecraft/world/damagesource | 12 | 0 | 936 |
com/mojang/math | 10 | 0 | 922 |
net/minecraft/client/color | 18 | 18 | 915 |
net/minecraft/commands/synchronization | 12 | 0 | 895 |
net/minecraft/server/dialog | 35 | 0 | 886 |
net/minecraft/data/advancements | 10 | 0 | 874 |
net/minecraft/stats | 10 | 0 | 873 |
net/minecraft/world/ticks | 14 | 0 | 869 |
net/minecraft/core/particles | 21 | 0 | 860 |
net/minecraft/util/thread | 9 | 0 | 842 |
net/minecraft/server/rcon | 9 | 0 | 839 |
net/minecraft/client/server | 6 | 6 | 838 |
net/minecraft/core/registries | 4 | 0 | 817 |
net/minecraft/commands/execution | 18 | 0 | 756 |
net/minecraft/world/waypoints | 9 | 0 | 677 |
net/minecraft/client/tutorial | 10 | 10 | 671 |
net/minecraft/util/valueproviders | 18 | 0 | 670 |
net/minecraft/commands/functions | 7 | 0 | 630 |
net/minecraft/gizmos | 15 | 0 | 569 |
net/minecraft/data/info | 8 | 0 | 530 |
net/minecraft/client/main | 4 | 4 | 523 |
net/minecraft/network/syncher | 6 | 0 | 522 |
net/minecraft/nbt/visitors | 7 | 0 | 512 |
net/minecraft/client/animation | 23 | 23 | 509 |
net/minecraft/client/searchtree | 8 | 8 | 505 |
net/minecraft/data/structures | 5 | 0 | 469 |
com/mojang/blaze3d | 12 | 12 | 462 |
net/minecraft/util/eventlog | 4 | 0 | 459 |
net/minecraft/world/timeline | 5 | 0 | 447 |
com/mojang/blaze3d/framegraph | 3 | 3 | 437 |
com/mojang/blaze3d/font | 7 | 7 | 432 |
net/minecraft/server/permissions | 12 | 0 | 402 |
net/minecraft/util/random | 4 | 0 | 369 |
net/minecraft/world/flag | 7 | 0 | 356 |
net/minecraft/server/chase | 3 | 0 | 355 |
net/minecraft/world/clock | 10 | 0 | 351 |
net/minecraft/recipebook | 3 | 0 | 350 |
net/minecraft/server/bossevents | 3 | 0 | 328 |
com/mojang/blaze3d/buffers | 6 | 6 | 327 |
net/minecraft/server/gui | 4 | 0 | 310 |
net/minecraft/core/cauldron | 3 | 0 | 307 |
net/minecraft/server/notifications | 5 | 0 | 295 |
net/minecraft/client/quickplay | 3 | 3 | 284 |
net/minecraft/world/food | 5 | 0 | 283 |
net/minecraft/client/input | 8 | 8 | 282 |
net/minecraft/data/registries | 5 | 0 | 248 |
net/minecraft/locale | 3 | 0 | 247 |
net/minecraft/client/entity | 4 | 4 | 228 |
net/minecraft/realms | 4 | 4 | 203 |
net/minecraft/util/debugchart | 8 | 0 | 199 |
com/mojang/blaze3d/util | 2 | 2 | 189 |
net/minecraft/util/context | 4 | 0 | 187 |
com/mojang/blaze3d/resource | 6 | 6 | 185 |
net/minecraft/server/waypoints | 2 | 0 | 180 |
com/mojang/blaze3d/preprocessor | 2 | 2 | 164 |
com/mojang/blaze3d/textures | 6 | 6 | 150 |
net/minecraft/util/monitoring | 2 | 0 | 120 |
net/minecraft/server/advancements | 2 | 0 | 87 |
net/minecraft/client/profiling | 2 | 2 | 81 |
com/mojang/blaze3d/shaders | 5 | 5 | 70 |
com/mojang/realmsclient/exception | 5 | 5 | 64 |
net/minecraft/data/metadata | 2 | 0 | 63 |
net/minecraft/client/waypoints | 2 | 2 | 44 |
net/minecraft/gametest | 2 | 0 | 19 |
| total | 7055 | 2206 | 719,302 |
Biggest classes
biggest — 26.2
| class | lines | side |
|---|---|---|
net/minecraft/world/entity/Entity | 4,464 | shared |
net/minecraft/world/entity/LivingEntity | 4,321 | shared |
net/minecraft/client/Minecraft | 3,274 | client |
net/minecraft/client/data/models/BlockModelGenerators | 3,090 | client |
net/minecraft/client/multiplayer/ClientPacketListener | 3,051 | client |
net/minecraft/server/MinecraftServer | 2,632 | shared |
net/minecraft/server/network/ServerGamePacketListenerImpl | 2,499 | shared |
net/minecraft/server/level/ServerPlayer | 2,445 | shared |
net/minecraft/util/datafix/fixes/BlockStateData | 2,270 | shared |
net/minecraft/server/level/ServerLevel | 2,126 | shared |
net/minecraft/world/entity/player/Player | 2,053 | shared |
net/minecraft/sounds/SoundEvents | 2,000 | shared |
net/minecraft/world/level/levelgen/structure/structures/OceanMonumentPieces | 1,983 | shared |
net/minecraft/client/Options | 1,972 | client |
net/minecraft/world/level/block/Blocks | 1,969 | shared |
net/minecraft/world/item/CreativeModeTabs | 1,706 | shared |
net/minecraft/world/item/Items | 1,694 | shared |
net/minecraft/server/level/ChunkMap | 1,668 | shared |
net/minecraft/world/entity/animal/fox/Fox | 1,625 | shared |
net/minecraft/world/entity/Mob | 1,612 | shared |
net/minecraft/util/datafix/DataFixers | 1,582 | shared |
net/minecraft/network/FriendlyByteBuf | 1,546 | shared |
net/minecraft/client/gui/Hud | 1,478 | client |
net/minecraft/world/entity/animal/bee/Bee | 1,451 | shared |
net/minecraft/world/level/levelgen/DensityFunctions | 1,445 | shared |
net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces | 1,436 | shared |
net/minecraft/util/Util | 1,415 | shared |
net/minecraft/client/player/LocalPlayer | 1,392 | client |
com/mojang/realmsclient/RealmsMainScreen | 1,376 | client |
net/minecraft/data/loot/packs/VanillaBlockLoot | 1,360 | shared |
net/minecraft/world/level/block/state/BlockBehaviour | 1,357 | shared |
net/minecraft/gametest/framework/GameTestHelper | 1,353 | shared |
net/minecraft/client/multiplayer/ClientLevel | 1,320 | client |
net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces | 1,317 | shared |
net/minecraft/world/item/ItemStack | 1,253 | shared |
net/minecraft/world/level/levelgen/structure/structures/WoodlandMansionPieces | 1,185 | shared |
net/minecraft/world/entity/animal/panda/Panda | 1,121 | shared |
net/minecraft/world/entity/animal/equine/AbstractHorse | 1,114 | shared |
net/minecraft/client/gui/screens/inventory/CreativeModeInventoryScreen | 1,101 | client |
net/minecraft/world/entity/monster/cubemob/SulfurCube | 1,082 | shared |
Hierarchy
hierarchy — 26.2
| root | descendants | direct | kind | where |
|---|---|---|---|---|
FeatureElement | 386 | 7 | interface | net/minecraft/world/flag |
ItemLike | 366 | 2 | interface | net/minecraft/world/level |
BlockBehaviour | 294 | 1 | class | net/minecraft/world/level/block/state |
Block | 293 | 92 | class | net/minecraft/world/level/block |
TypedInstance | 245 | 4 | interface | net/minecraft/core |
DebugValueSource | 244 | 3 | interface | net/minecraft/util/debug |
SlotProvider | 222 | 2 | interface | net/minecraft/world/entity |
Nameable | 208 | 6 | interface | net/minecraft/world |
TabOrderedElement | 196 | 2 | interface | net/minecraft/client/gui/components |
Renderable | 195 | 7 | interface | net/minecraft/client/gui/components |
GuiEventListener | 194 | 4 | interface | net/minecraft/client/gui/components/events |
DataComponentGetter | 194 | 4 | interface | net/minecraft/core/component |
ItemOwner | 190 | 2 | interface | net/minecraft/world/entity |
UniquelyIdentifyable | 190 | 1 | interface | net/minecraft/world/level/entity |
SyncedDataHolder | 189 | 1 | interface | net/minecraft/network/syncher |
ScoreHolder | 189 | 1 | interface | net/minecraft/world/scores |
EntityAccess | 189 | 1 | interface | net/minecraft/world/level/entity |
Entity | 188 | 18 | class | net/minecraft/world/entity |
ContainerEventHandler | 172 | 2 | interface | net/minecraft/client/gui/components/events |
Model | 168 | 12 | class | net/minecraft/client/model |
AbstractContainerEventHandler | 154 | 1 | class | net/minecraft/client/gui/components/events |
Screen | 153 | 71 | class | net/minecraft/client/gui/screens |
EntityModel | 153 | 70 | class | net/minecraft/client/model |
EntityRenderer | 129 | 27 | class | net/minecraft/client/renderer/entity |
Waypoint | 127 | 2 | interface | net/minecraft/world/waypoints |
Attackable | 126 | 2 | interface | net/minecraft/world/entity |
WaypointTransmitter | 125 | 1 | interface | net/minecraft/world/waypoints |
LivingEntity | 124 | 3 | class | net/minecraft/world/entity |
Leashable | 121 | 2 | interface | net/minecraft/world/entity |
Targeting | 116 | 2 | interface | net/minecraft/world/entity |
EquipmentUser | 115 | 1 | interface | net/minecraft/world/entity |
Mob | 114 | 5 | class | net/minecraft/world/entity |
Packet | 110 | 108 | interface | net/minecraft/network/protocol |
PathfinderMob | 108 | 5 | class | net/minecraft/world/entity |
NamespacedSchema | 103 | 103 | class | net/minecraft/util/datafix/schemas |
EntityRenderState | 98 | 22 | class | net/minecraft/client/renderer/entity/state |
RenderLayerParent | 97 | 1 | interface | net/minecraft/client/renderer/entity |
LivingEntityRenderer | 96 | 3 | class | net/minecraft/client/renderer/entity |
MobRenderer | 93 | 39 | class | net/minecraft/client/renderer/entity |
StringRepresentable | 81 | 81 | interface | net/minecraft/util |
Fan-in
fanin — 26.2
| class | imported by |
|---|---|
BlockPos (net.minecraft.core) | 1221 |
Identifier (net.minecraft.resources) | 882 |
BlockState (net.minecraft.world.level.block.state) | 862 |
RandomSource (net.minecraft.util) | 773 |
Component (net.minecraft.network.chat) | 766 |
Level (net.minecraft.world.level) | 750 |
ServerLevel (net.minecraft.server.level) | 726 |
ItemStack (net.minecraft.world.item) | 682 |
Mth (net.minecraft.util) | 677 |
Entity (net.minecraft.world.entity) | 667 |
Holder (net.minecraft.core) | 597 |
Vec3 (net.minecraft.world.phys) | 575 |
Direction (net.minecraft.core) | 570 |
LivingEntity (net.minecraft.world.entity) | 533 |
StreamCodec (net.minecraft.network.codec) | 508 |
Util (net.minecraft.util) | 454 |
Player (net.minecraft.world.entity.player) | 449 |
Registries (net.minecraft.core.registries) | 448 |
ResourceKey (net.minecraft.resources) | 402 |
SoundEvents (net.minecraft.sounds) | 316 |
BlockBehaviour (net.minecraft.world.level.block.state) | 311 |
Blocks (net.minecraft.world.level.block) | 310 |
Packet (net.minecraft.network.protocol) | 287 |
Minecraft (net.minecraft.client) | 280 |
ByteBufCodecs (net.minecraft.network.codec) | 275 |
EntityType (net.minecraft.world.entity) | 259 |
Block (net.minecraft.world.level.block) | 257 |
BuiltInRegistries (net.minecraft.core.registries) | 257 |
DataComponents (net.minecraft.core.component) | 253 |
PacketType (net.minecraft.network.protocol) | 241 |
SoundEvent (net.minecraft.sounds) | 232 |
RegistryFriendlyByteBuf (net.minecraft.network) | 229 |
ServerPlayer (net.minecraft.server.level) | 223 |
LevelReader (net.minecraft.world.level) | 220 |
BlockGetter (net.minecraft.world.level) | 217 |
Items (net.minecraft.world.item) | 216 |
SoundSource (net.minecraft.sounds) | 210 |
VoxelShape (net.minecraft.world.phys.shapes) | 206 |
Item (net.minecraft.world.item) | 204 |
EntityTypes (net.minecraft.world.entity) | 204 |
FriendlyByteBuf (net.minecraft.network) | 202 |
GuiGraphicsExtractor (net.minecraft.client.gui) | 195 |
Registry (net.minecraft.core) | 192 |
ModelPart (net.minecraft.client.model.geom) | 191 |
PoseStack (com.mojang.blaze3d.vertex) | 191 |
ExtraCodecs (net.minecraft.util) | 189 |
InteractionResult (net.minecraft.world) | 188 |
ValueOutput (net.minecraft.world.level.storage) | 184 |
BlockTags (net.minecraft.tags) | 183 |
ValueInput (net.minecraft.world.level.storage) | 183 |
DamageSource (net.minecraft.world.damagesource) | 182 |
MemoryModuleType (net.minecraft.world.entity.ai.memory) | 182 |
CommandSourceStack (net.minecraft.commands) | 178 |
ClientLevel (net.minecraft.client.multiplayer) | 173 |
StateDefinition (net.minecraft.world.level.block.state) | 171 |
ChunkPos (net.minecraft.world.level) | 170 |
CommonComponents (net.minecraft.network.chat) | 170 |
StringRepresentable (net.minecraft.util) | 167 |
AABB (net.minecraft.world.phys) | 165 |
BlockEntity (net.minecraft.world.level.block.entity) | 163 |
Anatomy
Verified against Minecraft 26.2 · Lecture 1 · From
main()to a running singleplayer world: which threads exist, which loop each one runs, and how the two halves of the game talk.
Responsibility
Java Minecraft is one codebase that runs as two programs. The client is a window, a frame loop and a copy of the world it is told about. The server is the world itself: a 20 Hz tick loop that owns every chunk, entity and block and is the only thing allowed to change them. In singleplayer both run in the same JVM, on different threads, and talk to each other through a real Netty connection that never touches a socket. A dedicated server is the same server class with the client half absent.
The one sentence a player recognises: the server is the game; the client is a view of it.
The data it owns
Minecraft(client, one instance,Minecraft.getInstance) owns the window (Window), the resource system (ReloadableResourceManagerand every manager registered on it —TextureManager,ShaderManager,ModelManager,AtlasManager,FontManager,SoundManager), the renderers (GameRenderer,LevelRenderer,EntityRenderDispatcher,BlockEntityRenderDispatcher,ParticleEngine), input (MouseHandler,KeyboardHandler), the HUD (Gui) andOptions. Three fields are nullable and define “are we in a world”:Minecraft.level(aClientLevel),Minecraft.player(aLocalPlayer) andMinecraft.gameMode(aMultiPlayerGameMode). A fourth,Minecraft.singleplayerServer, is theIntegratedServerwhen one is running.MinecraftServer(abstract;IntegratedServerandDedicatedServerare the two concrete kinds) owns the levels (ServerLevel, one per dimension), thePlayerList, theServerConnectionListener, theServerFunctionManager, theServerTickRateManagerand aPacketProcessor.- Nothing on the client writes server state and nothing on the server writes client state, ever, even in singleplayer. Everything crosses as a packet.
When it runs
There are two loops and they are not the same shape.
The client loop runs on the Render thread — the JVM main thread, renamed
in Main before Minecraft is constructed, and the thread RenderSystem
guards with RenderSystem.assertOnRenderThread. Minecraft.run loops on
Minecraft.runTick once per frame, as fast as vsync or the frame-rate
limit allow. Inside each frame a DeltaTracker.Timer (20 ticks per second)
says how many whole game ticks have elapsed since the last frame — usually 0
or 1, at most 10 are run — and Minecraft.tick is called that many times.
The fractional remainder is partialTick, which the renderers use to
interpolate between the last two tick states. So the client has a 20 Hz
tick, but it is a sub-step of the frame loop, not a loop of its own.
The server loop runs on the Server thread, created by
MinecraftServer.spin. MinecraftServer.runServer loops on
MinecraftServer.tickServer at a fixed 50 ms cadence tracked in nanoseconds:
after each tick it calls MinecraftServer.waitUntilNextTick, which spends the
slack running queued tasks and then blocks until the next tick is due. If a
tick overruns, the next one starts immediately; if the server falls more than
two seconds (one second plus 20 ticks) behind it logs Can’t keep up! and skips ahead rather than
trying to catch up. There is no frame and no partialTick on the server.
Both loops are event loops first and game loops second. MinecraftServer
extends ReentrantBlockableEventLoop, and Minecraft embeds the same idea:
each is an Executor whose queue drains on its own thread, so any other thread
that wants to touch game state submits a task and waits. The blocking form,
BlockableEventLoop.managedBlock, is how the owning thread waits for a
future without deadlocking — it keeps draining its own queue while it waits.
The threads
| Thread | Made by | Runs | Notes |
|---|---|---|---|
| Render thread | JVM main, renamed in client/main/Main | Minecraft.run | Also the client “game thread”: Minecraft.gameThread is this thread. Priority 10 on machines with more than 4 cores. |
| Server thread | MinecraftServer.spin | MinecraftServer.runServer | One per server, so singleplayer has exactly one. Priority 8. |
| Netty IO | EventLoopGroupHolder | the Netty pipeline | Named “Netty NIO IO #n” (or Epoll/Kqueue when native transport is on); “Netty Local IO #n” for the in-process singleplayer channel. Decode, encrypt, compress; never game logic. |
| Worker-Main-n | Util.backgroundExecutor | a ForkJoinPool sized to the core count | The shared CPU pool. Chunk generation and lighting (ChunkMap through ChunkTaskDispatcher), section meshing (SectionRenderDispatcher), resource reloads, chunk serialisation all run here. |
| IO-Worker-n | Util.ioPool | region-file reads and writes | Fed through IOWorker, one PriorityConsecutiveExecutor per storage kind so writes to one file stay ordered. Util.nonCriticalIoPool is the same idea for downloads. |
| Sound engine | SoundEngineExecutor | a BlockableEventLoop for OpenAL | The client’s third event loop. |
| Server Watchdog | DedicatedServer.initServer | ServerWatchdog | Dedicated only. Kills the JVM if a tick exceeds DedicatedServerProperties.maxTickTime (default one minute). |
| Server console handler | DedicatedServer.initServer | reads stdin | Commands typed at the console are queued to the server thread, not run on this one. |
| Timer hack thread | Util.startTimerHackThread | sleeps forever | A daemon thread that does nothing but sleep — the long-standing workaround for keeping the JVM’s timer resolution high while a sleeping thread exists. Both Main classes start it. |
Everything else that matters is serialised onto one of these. The two
ConsecutiveExecutor classes in util/thread are the mechanism: a queue that
promises to run its tasks one at a time on a pool that otherwise runs many —
which is how “worldgen”, “light” and the IO workers stay ordered without
owning a thread.
The trace: launching the game and opening a singleplayer world
sequenceDiagram
participant Main as Main (client/main)
participant MC as Minecraft
participant RS as RenderSystem
participant IS as IntegratedServer
participant MS as MinecraftServer
participant SCL as ServerConnectionListener
participant C as Connection
Main->>Main: tryDetectVersion, loadLibraries, Bootstrap.bootStrap — registries frozen before anything else exists
Main->>RS: initRenderThread — this thread is now the Render thread
Main->>MC: new Minecraft(GameConfig) — pick a GpuBackend, open the Window, register every reload listener, start the first resource reload
Main->>MC: run — the frame loop; never returns until quit
loop every frame
MC->>MC: runTick — drain PacketProcessor, then 0..n × tick, then render with partialTick
end
MC->>MS: doWorldLoad → spin(IntegratedServer) — the Server thread is born here
MS->>IS: runServer → initServer → loadLevel, prepareLevels
MC->>SCL: startMemoryChannel — a Netty LocalServerChannel, no socket
MC->>C: connectToLocalServer — the client's side of the same channel
C->>SCL: handshake → login → configuration → play, as packets
loop every 50 ms on the Server thread
MS->>MS: tickServer — packets, then tickChildren (each ServerLevel.tick), then tickConnection
MS->>MS: waitUntilNextTick — run queued tasks, sleep the remainder
end
Note over MC,IS: IntegratedServer.tickServer pauses the world when Minecraft.isPaused and nobody else is connected
Narrated:
- Bootstrap before anything.
SharedConstants.tryDetectVersionreadsversion.json;Bootstrap.bootStrapbuilds and freezes the static registries (blocks, items, entity types — the things that cannot be data-driven because the data loader itself needs them);ClientBootstrapdoes the client-only equivalents. BothMainclasses do this first, which is why nothing inworld/can be touched from a static initialiser. - The GPU backend is chosen in the constructor.
PreferredGraphicsApifromOptionsdecides the orderPreferredGraphicsApi.getBackendsToTryreturns: OpenGL first by default, Vulkan first only if the player opts in; the firstGpuBackend(GlBackendorVulkanBackend) that can create aWindowwins, and from then on the renderer only ever sees theGpuDeviceabstraction incom/mojang/blaze3d. - Construction registers, it does not load. The constructor creates each
manager and registers it on the
ReloadableResourceManager; the actual loading is oneReloadInstancerun onUtil.backgroundExecutorwith theLoadingOverlayon screen. Resource reloads (F3+T) are the same path re-run. Minecraft.runis the frame loop.Minecraft.runTickpolls GLFW events, drains thePacketProcessor, advances theDeltaTracker, runs the tick(s), and renders. Every frame does all of these; a tick is merely a thing that happens in some frames.- Opening a world spins a server.
Minecraft.doWorldLoadcallsMinecraftServer.spin, which creates the Server thread and constructs theIntegratedServeron the caller’s thread handing it the new thread object; the thread then runsMinecraftServer.runServer, which callsIntegratedServer.initServerand enters the loop. - The client connects like any other client.
ServerConnectionListener.startMemoryChannelbinds a NettyLocalAddress;Connection.connectToLocalServerconnects to it; the client then walks the same handshake → login → configuration → play state machine it would with a remote server, viaClientHandshakePacketListenerImpl. Nothing in the play path knows it is singleplayer. - Pause is a server decision.
IntegratedServer.tickServerchecksMinecraft.isPausedand whether other players are connected; when paused it runsIntegratedServer.tickPaused— connections only, no world tick — and saves once on the transition. A published LAN world therefore never pauses.
Interfaces
- Called by: the JVM.
client/main/Mainfor the client,server/Mainfor the dedicated server; the data generator has a thirdMainunderclient/data. - Calls into: everything; this page is the frame the rest hang on. The first lane of every later diagram is one of the thread names above.
- Crosses the network as: nothing of its own — but the thread crossing
for packets is defined here and every networking page relies on it. A packet
arrives on a Netty IO thread and
Connection.channelRead0hands it to the currentPacketListener; handlers that touch game state callPacketUtils.ensureRunningOnSameThread, which, when off-thread, queues the packet on the owning side’sPacketProcessorand aborts the handler withRunningOnDifferentThreadException. The queue is drained at the top of the next tick (PacketProcessor.processQueuedPackets, first thing in bothMinecraft.runTickandMinecraftServer.processPacketsAndTick). Sending is the reverse:Connection.sendwrites to the channel from any thread and Netty flushes on its own. - Data-driven by:
version.json(SharedConstants),options.txt(Options),server.properties(DedicatedServerProperties).
Invariants and surprises
- The Render thread is the game thread. There is no separate client
logic thread;
Minecraft.gameThreadand the threadRenderSystemasserts on are the same one. A slow client tick costs frames directly. - The server never renders and the client never simulates authoritatively.
ClientLeveldoes tick entities and block entities (Minecraft.tickcallsClientLevel.tickEntities) but only to predict and animate; the server’s packets overwrite whatever the prediction got wrong. - Singleplayer is multiplayer with a loopback.
IntegratedServeris aMinecraftServer; the connection is a NettyLocalChannel; the packets are real. The only singleplayer special cases are pausing and the view/simulation distance followingOptions. - One worker pool, many queues.
Util.backgroundExecutoris a singleForkJoinPool; the ordering guarantees the game needs (worldgen steps in order, light before mesh, one writer per region file) come fromConsecutiveExecutorandPriorityConsecutiveExecutorlayered on top, never from dedicated threads. MinecraftServer.haveTimeis the budget.MinecraftServer.tickServerreceives aBooleanSupplier(MinecraftServer.haveTime) and passes it down toServerLevel.tick; chunk loading and other deferrable work check it and stop when the 50 ms are spent. While sprinting (/tick sprint) the supplier is always false, so nothing deferrable runs and ticks go as fast as they can.- The tick rate is a server field, not a constant.
ServerTickRateManager(shared baseTickRateManager) owns the nanoseconds-per-tick, freeze and sprint state that/tickmanipulates; the client mirrors it inClientLevelsoDeltaTrackercan freeze too. - Crashes are collected, not thrown. Both loops catch everything, wrap it
in a
CrashReport, and on the client try an emergency save first; a background thread that dies reports throughBlockableEventLoop.delayCrashso the crash surfaces on the owning thread.
Where to look
client/main/Main · Minecraft · DeltaTracker · MinecraftServer ·
IntegratedServer · server/Main · DedicatedServer ·
BlockableEventLoop · Util (the executors) · PacketProcessor ·
PacketUtils · EventLoopGroupHolder · ServerConnectionListener ·
Connection · PreferredGraphicsApi · GpuBackend
Sound
Verified against Minecraft 26.2 · A block breaks near you and you hear it: from
Level.playSoundon the server to an OpenAL source on the Sound engine thread.
Responsibility
The sound system turns events (“a stone block broke at this position”) into
audio (a decoded .ogg playing on an OpenAL source at a volume that falls
off with distance from the camera). It is entirely client-side: the server
never decodes, mixes or knows what a sound is beyond an Identifier and a
range. It is also the smallest complete system in the game — one resource
loader, one engine, one thread, one native library — which is why it is the
first system after Anatomy.
The one sentence a player recognises: the server says “play this here”, the client decides whether you can hear it.
The data it owns
SoundEvent(innet/minecraft/sounds, shared) is a record of anIdentifierand an optional fixed range.SoundEventsis the 2,000-line static registry of every one the game defines. It is a name, not a file.SoundSourceis the volume category —SoundSource.MASTER,SoundSource.MUSIC,SoundSource.BLOCKS,SoundSource.HOSTILE,SoundSource.PLAYERS,SoundSource.AMBIENT,SoundSource.VOICE,SoundSource.UI… — each an options slider.sounds.json, one per namespace in every resource pack, maps a sound event name to aSoundEventRegistration: a weighted list ofSoundentries (a file, or a redirect to another event — theSoundtype enum), each with volume, pitch, weight, attenuation distance, and whether to stream rather than load whole.SoundManagerowns the loaded form, a map ofIdentifier→WeighedSoundEvents, rebuilt on every resource reload.SoundInstance(client/resources/sounds) is one playing or wanting to play sound: event, source, volume, pitch, position, looping, relative, attenuation.SimpleSoundInstanceis a one-shot at a point;EntityBoundSoundInstancefollows an entity;TickableSoundInstancesubclasses (AbstractTickableSoundInstance, minecarts, elytra, bees, ambient loops) re-evaluate themselves every tick.SoundEngineowns the runtime state: which instances are playing (SoundEngine.instanceToChannel), grouped by source (SoundEngine.instanceBySource), delayed (SoundEngine.queuedSounds) and ticking (SoundEngine.tickingSounds); the per-category gains; theSoundBufferLibrarycache of decoded buffers; and theLibrary, which owns the OpenAL device, context and the source pools.com/mojang/blaze3d/audiois the OpenAL wrapper:Library(device, context, listener, channel pools),Channel(one OpenAL source),SoundBuffer(one OpenAL buffer),Listener(the ear — position and orientation), and aDeviceTrackerthat notices headphones being unplugged.
Nothing outside client/sounds touches OpenAL. Everything else calls
SoundManager.play and forgets.
When it runs
Four threads take part, and the page is mostly about which does what.
- Server thread: decides a sound happens (
ServerLevel.playSeededSound), computes who is in range, sends packets. Never audio. - Render thread (the client game thread): receives the packet, builds a
SoundInstance, callsSoundManager.play→SoundEngine.play. Once per client tickMinecraft.tickcallsSoundManager.tick, which walks the ticking sounds, updates positions and volumes, and expires finished channels; once per frameMinecraft.runTickcallsSoundManager.updateSourcewith the camera so the listener moves smoothly.MusicManager.tickalso runs here, choosing and fading background music. - Sound engine thread: a
SoundEngineExecutor, which is aBlockableEventLoop— the same event-loop pattern as the server thread — wrapped around a single daemon thread named “Sound engine”. Every OpenAL call is a task on this executor, submitted throughChannelAccess. The Render thread never calls OpenAL itself. Util.nonCriticalIoPool(the “Download-” threads): reads and decodes.oggfiles (JOrbisAudioStream, a Java Vorbis decoder) into aSoundBuffer, insideSoundBufferLibrary.getCompleteBuffer; the result is aCompletableFuturethat, on completion, schedules the “attach and play” task onto the sound thread.
The trace: a block breaks and you hear it
sequenceDiagram
participant SL as ServerLevel (Server thread)
participant PL as PlayerList
participant CPL as ClientPacketListener (Render thread)
participant CL as ClientLevel
participant SM as SoundManager
participant SE as SoundEngine
participant SBL as SoundBufferLibrary (IO pool)
participant CA as ChannelAccess (Sound engine thread)
participant Lib as Library / Channel (OpenAL)
SL->>SL: playSeededSound(except, x,y,z, SoundEvent, SoundSource, volume, pitch, seed)
SL->>PL: broadcast(except, x,y,z, SoundEvent.getRange(volume), dimension, ClientboundSoundPacket)
PL-->>CPL: ClientboundSoundPacket — to every player within range, except one
CPL->>CL: handleSoundEvent → playSeededSound (after ensureRunningOnSameThread)
CL->>SM: play(SimpleSoundInstance) — seeded, so every client picks the same variant
SM->>SE: play(instance)
SE->>SE: resolve WeighedSoundEvents, pick a Sound by weight, calculateVolume × category gain
SE->>CA: createHandle(STATIC or STREAMING pool) — a task on the sound thread
CA->>Lib: acquireChannel — an OpenAL source from the pool, or null if all 30 are busy
SE->>CA: handle.execute — setPitch, setVolume, linearAttenuation(range), setSelfPosition
SE->>SBL: getCompleteBuffer(path) — decode the .ogg on the IO pool (cached)
SBL-->>CA: thenAccept → handle.execute — attachStaticBuffer, play
loop every client tick
SE->>CA: scheduleTick — pump streams, release stopped channels
end
Narrated:
- The server picks who hears it.
ServerLevel.playSeededSoundasksSoundEvent.getRangefor the audible radius — a fixed range if the event declares one, otherwise 16 blocks scaled up by volumes above 1 — andPlayerList.broadcastsendsClientboundSoundPacketto every player in that dimension within range, skipping the except player. The seed travels in the packet so that all clients pick the same random variant and pitch. - The client receives it on the game thread.
ClientPacketListener.handleSoundEventgoes throughPacketUtils.ensureRunningOnSameThread(see Anatomy) and callsClientLevel.playSeededSound, which builds aSimpleSoundInstanceand hands it toSoundManager.play. SoundEngine.playresolves the name to a file. The instance’sIdentifieris looked up in theSoundManagerregistry to get aWeighedSoundEvents;WeighedSoundEvents.getSoundrolls the weighted choice (following event-to-event redirects) to a concreteSound; volume is multiplied by the category gain and the master gain; a zero volume returns early with not started rather than occupying a channel. Any registeredSoundEventListeneris told first —SubtitleOverlayis the only one, which is how subtitles work.- A channel is borrowed on the sound thread.
ChannelAccess.createHandleposts a task to theSoundEngineExecutor; on that threadLibraryacquires aChannelfrom the static or streaming pool (chosen bySound.shouldStream). The Render thread blocks on that future — the one place the game thread waits on the sound thread — and gets a channel handle, or null when every source is in use, in which case the sound is silently dropped. - Parameters are set, then the data arrives later. The handle’s
ChannelAccess.executeposts the pitch/volume/attenuation/position setup; separately,SoundBufferLibrary.getCompleteBufferreturns a future for the decoded buffer (cached per path; decoding runs onUtil.nonCriticalIoPool). When the buffer is ready its continuation posts “attach buffer, play” to the sound thread. A sound therefore starts one or two frames after the packet, never on the frame it arrives, unless the buffer is already cached — which is whatSound.shouldPreloadandSoundEngine.requestPreloadare for. - Streams are pumped by the tick. Long sounds (music, records) are
streamed:
Channel.attachBufferStreamqueues a few seconds of decoded audio, andChannelAccess.scheduleTick, posted once per client tick fromSoundEngine.tick, callsChannel.updateStreamon each to refill. The same pass releases channels whose source reports stopped. - The listener follows the camera.
SoundEngine.updateSourceposts aListenerTransform(position, forward, up) from theCamerato the sound thread every frame; OpenAL does the distance attenuation and panning from there.SoundInstance.isRelativesounds (UI clicks) are positioned relative to the listener instead, so they never attenuate.
Interfaces
- Called by: anything with a
Level— blocks, entities, items — throughLevel.playSound;PlaySoundCommand;MusicManagerfor music; the ambient handlers inclient/resources/sounds(BiomeAmbientSoundsHandler,UnderwaterAmbientSoundHandler,BubbleColumnAmbientSoundHandler) for loops that exist only on the client. - Calls into:
com/mojang/blaze3d/audio→ LWJGL’s OpenAL bindings;JOrbisAudioStream→ JOrbis for Vorbis decoding; the resource system forsounds.jsonand the.oggfiles. - Crosses the network as:
ClientboundSoundPacket(a point in space),ClientboundSoundEntityPacket(attached to an entity, handled byClientPacketListener.handleSoundEntityEvent),ClientboundStopSoundPacket(/stopsound). All clientbound. There is no serverbound sound packet: the server infers what you did from other packets and tells others about the sound. - Data-driven by:
sounds.json(resource packs, so the client’s own), theRegistries.SOUND_EVENTregistry (static; data packs cannot add sound events, only reference them), andoptions.txtfor category volumes, device, and HRTF.
Invariants and surprises
- Your own sounds are predicted, not received.
Player.playSoundcallsLevel.playSoundwith itself as the except entity. On the server that broadcasts to everyone but you; on the client,ClientLevel.playSeededSoundseesexcept == Minecraft.playerand plays it locally at once. So the sound of your own footsteps, hits and block breaks never round-trips — and a laggy connection delays what you hear of others, never of yourself.LocalPlayer.playSoundgoes further and callsClientLevel.playLocalSounddirectly. - The sound thread is an event loop, not a mixer.
SoundEngineExecutordoes nothing but run tasks; OpenAL (the native library) does the mixing on its own threads. The Java thread exists only so that AL calls are serialised and never made from the Render thread. - Thirty sources, split by square root.
Libraryasks the device how many mono sources it offers (default 30), gives clamp(√n, 2, 8) of them to the streaming pool and the rest to the static pool. When a pool is empty new sounds are dropped, not queued; the game does not steal channels by priority. - Volume zero is not played.
SoundEngine.playreturns not started for a computed volume of 0 unless the instance saysSoundInstance.canStartSilent— which is why ticking sounds that fade in must opt in, and why muting a category frees its channels rather than playing silence. - Looping is two mechanisms. Static sounds loop in OpenAL
(
Channel.setLooping); streamed sounds loop by wrapping the decoder in aLoopingAudioStream, since the source only ever holds a few seconds. - Attenuation is linear and per-sound.
SoundInstance.getAttenuationis linear or none; the distance isSound.getAttenuationDistance(16 by default, fromsounds.json) scaled by volume. The server’s range and the client’s attenuation are computed separately from the same numbers — a sound can be sent and inaudible, or (with a resource pack) audible beyond where the server would send it. - Reload is destroy-and-rebuild.
SoundManager.reloadandSoundEngine.reloadstop everything, tear down the OpenAL context inLibrary.cleanup, andSoundEngine.loadLibraryagain. Changing the output device in options, or theDeviceTrackernoticing a device change, takes the same path — so sounds cut out for a moment when you plug in headphones.
Where to look
SoundManager · SoundEngine · SoundInstance · SimpleSoundInstance ·
EntityBoundSoundInstance · WeighedSoundEvents · Sound ·
SoundEngineExecutor · ChannelAccess · SoundBufferLibrary ·
JOrbisAudioStream · Library · Channel · Listener · MusicManager ·
ServerLevel.playSeededSound · PlayerList.broadcast ·
ClientboundSoundPacket
Lectures
The video series. Each lecture is an ordering over the system pages, which are the stable content; the order here may change without the pages changing. Planned, not scheduled.