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

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)classesclient-onlylines
net/minecraft/world25850304,897
net/minecraft/client18641864172,711
net/minecraft/util717053,275
net/minecraft/server420052,720
com/mojang/blaze3d21121126,111
net/minecraft/network411023,378
net/minecraft/data163015,587
com/mojang/realmsclient12712713,217
net/minecraft/commands122013,001
net/minecraft/core110011,239
net/minecraft/nbt4308,001
net/minecraft/advancements11607,735
net/minecraft/gametest4705,514
net/minecraft/sounds602,101
net/minecraft/tags2901,788
net/minecraft/resources1501,764
net/minecraft1901,665
net/minecraft/references501,434
com/mojang/math100922
net/minecraft/stats100873
net/minecraft/gizmos150569
net/minecraft/recipebook30350
net/minecraft/locale30247
net/minecraft/realms44203
total70552206719,302
package (depth 4)classesclient-onlylines
net/minecraft/world/level13120146,417
net/minecraft/world/entity7160109,061
net/minecraft/client/gui44444459,057
net/minecraft/client/renderer70170148,412
net/minecraft/world/item314029,281
net/minecraft/util/datafix396026,372
net/minecraft/network/protocol293012,934
net/minecraft/server/commands102012,781
net/minecraft/client/model26726712,696
net/minecraft/util91012,067
net/minecraft/server/level42011,977
net/minecraft/client/multiplayer636311,169
net/minecraft/client414110,709
net/minecraft/commands/arguments7108,847
net/minecraft/client/resources1011017,612
net/minecraft/nbt3607,489
com/mojang/blaze3d/vulkan40407,477
net/minecraft/world/inventory6407,082
net/minecraft/client/particle87876,806
net/minecraft/client/data28286,176
com/mojang/realmsclient/gui40405,830
com/mojang/blaze3d/opengl28285,627
net/minecraft/gametest/framework4505,495
net/minecraft/core3805,493
net/minecraft/server/network2805,409
net/minecraft/data/worldgen5605,369
net/minecraft/server2705,227
net/minecraft/server/packs5504,975
net/minecraft/network/chat6304,818
net/minecraft/util/profiling7004,260
net/minecraft/server/jsonrpc6504,094
com/mojang/blaze3d/platform29293,896
net/minecraft/network4203,732
net/minecraft/util/filefix5703,544
net/minecraft/advancements/predicates5403,459
net/minecraft/world/phys2803,020
net/minecraft/data/loot2102,883
net/minecraft/advancements/triggers4902,809
net/minecraft/server/players1902,766
net/minecraft/core/component3002,668
com/mojang/realmsclient/client19192,534
com/mojang/blaze3d/vertex16162,420
com/mojang/blaze3d/systems26262,308
net/minecraft/data/recipes1702,227
net/minecraft/client/sounds17172,152
net/minecraft/sounds602,101
net/minecraft/util/parsing2901,879
net/minecraft/commands1401,873
com/mojang/realmsclient/dto39391,866
net/minecraft/client/player991,821
net/minecraft/server/dedicated701,809
net/minecraft/tags2901,788
net/minecraft/resources1501,764
net/minecraft/world/attribute2801,705
net/minecraft1901,665
net/minecraft/world2501,664
net/minecraft/data/tags2901,660
com/mojang/realmsclient441,524
net/minecraft/advancements1301,467
net/minecraft/world/scores1601,442
net/minecraft/references501,434
com/mojang/realmsclient/util20201,399
net/minecraft/network/codec701,372
net/minecraft/util/debug1901,370
net/minecraft/world/effect2001,306
net/minecraft/data1001,264
net/minecraft/client/telemetry18181,221
net/minecraft/core/dispenser1401,094
com/mojang/blaze3d/audio12121,013
com/mojang/blaze3d/pipeline1111954
net/minecraft/util/worldupdate60937
net/minecraft/world/damagesource120936
com/mojang/math100922
net/minecraft/client/color1818915
net/minecraft/commands/synchronization120895
net/minecraft/server/dialog350886
net/minecraft/data/advancements100874
net/minecraft/stats100873
net/minecraft/world/ticks140869
net/minecraft/core/particles210860
net/minecraft/util/thread90842
net/minecraft/server/rcon90839
net/minecraft/client/server66838
net/minecraft/core/registries40817
net/minecraft/commands/execution180756
net/minecraft/world/waypoints90677
net/minecraft/client/tutorial1010671
net/minecraft/util/valueproviders180670
net/minecraft/commands/functions70630
net/minecraft/gizmos150569
net/minecraft/data/info80530
net/minecraft/client/main44523
net/minecraft/network/syncher60522
net/minecraft/nbt/visitors70512
net/minecraft/client/animation2323509
net/minecraft/client/searchtree88505
net/minecraft/data/structures50469
com/mojang/blaze3d1212462
net/minecraft/util/eventlog40459
net/minecraft/world/timeline50447
com/mojang/blaze3d/framegraph33437
com/mojang/blaze3d/font77432
net/minecraft/server/permissions120402
net/minecraft/util/random40369
net/minecraft/world/flag70356
net/minecraft/server/chase30355
net/minecraft/world/clock100351
net/minecraft/recipebook30350
net/minecraft/server/bossevents30328
com/mojang/blaze3d/buffers66327
net/minecraft/server/gui40310
net/minecraft/core/cauldron30307
net/minecraft/server/notifications50295
net/minecraft/client/quickplay33284
net/minecraft/world/food50283
net/minecraft/client/input88282
net/minecraft/data/registries50248
net/minecraft/locale30247
net/minecraft/client/entity44228
net/minecraft/realms44203
net/minecraft/util/debugchart80199
com/mojang/blaze3d/util22189
net/minecraft/util/context40187
com/mojang/blaze3d/resource66185
net/minecraft/server/waypoints20180
com/mojang/blaze3d/preprocessor22164
com/mojang/blaze3d/textures66150
net/minecraft/util/monitoring20120
net/minecraft/server/advancements2087
net/minecraft/client/profiling2281
com/mojang/blaze3d/shaders5570
com/mojang/realmsclient/exception5564
net/minecraft/data/metadata2063
net/minecraft/client/waypoints2244
net/minecraft/gametest2019
total70552206719,302

Biggest classes

biggest — 26.2

classlinesside
net/minecraft/world/entity/Entity4,464shared
net/minecraft/world/entity/LivingEntity4,321shared
net/minecraft/client/Minecraft3,274client
net/minecraft/client/data/models/BlockModelGenerators3,090client
net/minecraft/client/multiplayer/ClientPacketListener3,051client
net/minecraft/server/MinecraftServer2,632shared
net/minecraft/server/network/ServerGamePacketListenerImpl2,499shared
net/minecraft/server/level/ServerPlayer2,445shared
net/minecraft/util/datafix/fixes/BlockStateData2,270shared
net/minecraft/server/level/ServerLevel2,126shared
net/minecraft/world/entity/player/Player2,053shared
net/minecraft/sounds/SoundEvents2,000shared
net/minecraft/world/level/levelgen/structure/structures/OceanMonumentPieces1,983shared
net/minecraft/client/Options1,972client
net/minecraft/world/level/block/Blocks1,969shared
net/minecraft/world/item/CreativeModeTabs1,706shared
net/minecraft/world/item/Items1,694shared
net/minecraft/server/level/ChunkMap1,668shared
net/minecraft/world/entity/animal/fox/Fox1,625shared
net/minecraft/world/entity/Mob1,612shared
net/minecraft/util/datafix/DataFixers1,582shared
net/minecraft/network/FriendlyByteBuf1,546shared
net/minecraft/client/gui/Hud1,478client
net/minecraft/world/entity/animal/bee/Bee1,451shared
net/minecraft/world/level/levelgen/DensityFunctions1,445shared
net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces1,436shared
net/minecraft/util/Util1,415shared
net/minecraft/client/player/LocalPlayer1,392client
com/mojang/realmsclient/RealmsMainScreen1,376client
net/minecraft/data/loot/packs/VanillaBlockLoot1,360shared
net/minecraft/world/level/block/state/BlockBehaviour1,357shared
net/minecraft/gametest/framework/GameTestHelper1,353shared
net/minecraft/client/multiplayer/ClientLevel1,320client
net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces1,317shared
net/minecraft/world/item/ItemStack1,253shared
net/minecraft/world/level/levelgen/structure/structures/WoodlandMansionPieces1,185shared
net/minecraft/world/entity/animal/panda/Panda1,121shared
net/minecraft/world/entity/animal/equine/AbstractHorse1,114shared
net/minecraft/client/gui/screens/inventory/CreativeModeInventoryScreen1,101client
net/minecraft/world/entity/monster/cubemob/SulfurCube1,082shared

Hierarchy

hierarchy — 26.2

rootdescendantsdirectkindwhere
FeatureElement3867interfacenet/minecraft/world/flag
ItemLike3662interfacenet/minecraft/world/level
BlockBehaviour2941classnet/minecraft/world/level/block/state
Block29392classnet/minecraft/world/level/block
TypedInstance2454interfacenet/minecraft/core
DebugValueSource2443interfacenet/minecraft/util/debug
SlotProvider2222interfacenet/minecraft/world/entity
Nameable2086interfacenet/minecraft/world
TabOrderedElement1962interfacenet/minecraft/client/gui/components
Renderable1957interfacenet/minecraft/client/gui/components
GuiEventListener1944interfacenet/minecraft/client/gui/components/events
DataComponentGetter1944interfacenet/minecraft/core/component
ItemOwner1902interfacenet/minecraft/world/entity
UniquelyIdentifyable1901interfacenet/minecraft/world/level/entity
SyncedDataHolder1891interfacenet/minecraft/network/syncher
ScoreHolder1891interfacenet/minecraft/world/scores
EntityAccess1891interfacenet/minecraft/world/level/entity
Entity18818classnet/minecraft/world/entity
ContainerEventHandler1722interfacenet/minecraft/client/gui/components/events
Model16812classnet/minecraft/client/model
AbstractContainerEventHandler1541classnet/minecraft/client/gui/components/events
Screen15371classnet/minecraft/client/gui/screens
EntityModel15370classnet/minecraft/client/model
EntityRenderer12927classnet/minecraft/client/renderer/entity
Waypoint1272interfacenet/minecraft/world/waypoints
Attackable1262interfacenet/minecraft/world/entity
WaypointTransmitter1251interfacenet/minecraft/world/waypoints
LivingEntity1243classnet/minecraft/world/entity
Leashable1212interfacenet/minecraft/world/entity
Targeting1162interfacenet/minecraft/world/entity
EquipmentUser1151interfacenet/minecraft/world/entity
Mob1145classnet/minecraft/world/entity
Packet110108interfacenet/minecraft/network/protocol
PathfinderMob1085classnet/minecraft/world/entity
NamespacedSchema103103classnet/minecraft/util/datafix/schemas
EntityRenderState9822classnet/minecraft/client/renderer/entity/state
RenderLayerParent971interfacenet/minecraft/client/renderer/entity
LivingEntityRenderer963classnet/minecraft/client/renderer/entity
MobRenderer9339classnet/minecraft/client/renderer/entity
StringRepresentable8181interfacenet/minecraft/util

Fan-in

fanin — 26.2

classimported 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 (ReloadableResourceManager and every manager registered on it — TextureManager, ShaderManager, ModelManager, AtlasManager, FontManager, SoundManager), the renderers (GameRenderer, LevelRenderer, EntityRenderDispatcher, BlockEntityRenderDispatcher, ParticleEngine), input (MouseHandler, KeyboardHandler), the HUD (Gui) and Options. Three fields are nullable and define “are we in a world”: Minecraft.level (a ClientLevel), Minecraft.player (a LocalPlayer) and Minecraft.gameMode (a MultiPlayerGameMode). A fourth, Minecraft.singleplayerServer, is the IntegratedServer when one is running.
  • MinecraftServer (abstract; IntegratedServer and DedicatedServer are the two concrete kinds) owns the levels (ServerLevel, one per dimension), the PlayerList, the ServerConnectionListener, the ServerFunctionManager, the ServerTickRateManager and a PacketProcessor.
  • 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

ThreadMade byRunsNotes
Render threadJVM main, renamed in client/main/MainMinecraft.runAlso the client “game thread”: Minecraft.gameThread is this thread. Priority 10 on machines with more than 4 cores.
Server threadMinecraftServer.spinMinecraftServer.runServerOne per server, so singleplayer has exactly one. Priority 8.
Netty IOEventLoopGroupHolderthe Netty pipelineNamed “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-nUtil.backgroundExecutora ForkJoinPool sized to the core countThe shared CPU pool. Chunk generation and lighting (ChunkMap through ChunkTaskDispatcher), section meshing (SectionRenderDispatcher), resource reloads, chunk serialisation all run here.
IO-Worker-nUtil.ioPoolregion-file reads and writesFed through IOWorker, one PriorityConsecutiveExecutor per storage kind so writes to one file stay ordered. Util.nonCriticalIoPool is the same idea for downloads.
Sound engineSoundEngineExecutora BlockableEventLoop for OpenALThe client’s third event loop.
Server WatchdogDedicatedServer.initServerServerWatchdogDedicated only. Kills the JVM if a tick exceeds DedicatedServerProperties.maxTickTime (default one minute).
Server console handlerDedicatedServer.initServerreads stdinCommands typed at the console are queued to the server thread, not run on this one.
Timer hack threadUtil.startTimerHackThreadsleeps foreverA 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:

  1. Bootstrap before anything. SharedConstants.tryDetectVersion reads version.json; Bootstrap.bootStrap builds and freezes the static registries (blocks, items, entity types — the things that cannot be data-driven because the data loader itself needs them); ClientBootstrap does the client-only equivalents. Both Main classes do this first, which is why nothing in world/ can be touched from a static initialiser.
  2. The GPU backend is chosen in the constructor. PreferredGraphicsApi from Options decides the order PreferredGraphicsApi.getBackendsToTry returns: OpenGL first by default, Vulkan first only if the player opts in; the first GpuBackend (GlBackend or VulkanBackend) that can create a Window wins, and from then on the renderer only ever sees the GpuDevice abstraction in com/mojang/blaze3d.
  3. Construction registers, it does not load. The constructor creates each manager and registers it on the ReloadableResourceManager; the actual loading is one ReloadInstance run on Util.backgroundExecutor with the LoadingOverlay on screen. Resource reloads (F3+T) are the same path re-run.
  4. Minecraft.run is the frame loop. Minecraft.runTick polls GLFW events, drains the PacketProcessor, advances the DeltaTracker, runs the tick(s), and renders. Every frame does all of these; a tick is merely a thing that happens in some frames.
  5. Opening a world spins a server. Minecraft.doWorldLoad calls MinecraftServer.spin, which creates the Server thread and constructs the IntegratedServer on the caller’s thread handing it the new thread object; the thread then runs MinecraftServer.runServer, which calls IntegratedServer.initServer and enters the loop.
  6. The client connects like any other client. ServerConnectionListener.startMemoryChannel binds a Netty LocalAddress; Connection.connectToLocalServer connects to it; the client then walks the same handshake → login → configuration → play state machine it would with a remote server, via ClientHandshakePacketListenerImpl. Nothing in the play path knows it is singleplayer.
  7. Pause is a server decision. IntegratedServer.tickServer checks Minecraft.isPaused and whether other players are connected; when paused it runs IntegratedServer.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/Main for the client, server/Main for the dedicated server; the data generator has a third Main under client/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.channelRead0 hands it to the current PacketListener; handlers that touch game state call PacketUtils.ensureRunningOnSameThread, which, when off-thread, queues the packet on the owning side’s PacketProcessor and aborts the handler with RunningOnDifferentThreadException. The queue is drained at the top of the next tick (PacketProcessor.processQueuedPackets, first thing in both Minecraft.runTick and MinecraftServer.processPacketsAndTick). Sending is the reverse: Connection.send writes 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.gameThread and the thread RenderSystem asserts on are the same one. A slow client tick costs frames directly.
  • The server never renders and the client never simulates authoritatively. ClientLevel does tick entities and block entities (Minecraft.tick calls ClientLevel.tickEntities) but only to predict and animate; the server’s packets overwrite whatever the prediction got wrong.
  • Singleplayer is multiplayer with a loopback. IntegratedServer is a MinecraftServer; the connection is a Netty LocalChannel; the packets are real. The only singleplayer special cases are pausing and the view/simulation distance following Options.
  • One worker pool, many queues. Util.backgroundExecutor is a single ForkJoinPool; the ordering guarantees the game needs (worldgen steps in order, light before mesh, one writer per region file) come from ConsecutiveExecutor and PriorityConsecutiveExecutor layered on top, never from dedicated threads.
  • MinecraftServer.haveTime is the budget. MinecraftServer.tickServer receives a BooleanSupplier (MinecraftServer.haveTime) and passes it down to ServerLevel.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 base TickRateManager) owns the nanoseconds-per-tick, freeze and sprint state that /tick manipulates; the client mirrors it in ClientLevel so DeltaTracker can 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 through BlockableEventLoop.delayCrash so 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.playSound on 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 (in net/minecraft/sounds, shared) is a record of an Identifier and an optional fixed range. SoundEvents is the 2,000-line static registry of every one the game defines. It is a name, not a file.
  • SoundSource is 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 a SoundEventRegistration: a weighted list of Sound entries (a file, or a redirect to another event — the Sound type enum), each with volume, pitch, weight, attenuation distance, and whether to stream rather than load whole. SoundManager owns the loaded form, a map of IdentifierWeighedSoundEvents, 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. SimpleSoundInstance is a one-shot at a point; EntityBoundSoundInstance follows an entity; TickableSoundInstance subclasses (AbstractTickableSoundInstance, minecarts, elytra, bees, ambient loops) re-evaluate themselves every tick.
  • SoundEngine owns 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; the SoundBufferLibrary cache of decoded buffers; and the Library, which owns the OpenAL device, context and the source pools.
  • com/mojang/blaze3d/audio is the OpenAL wrapper: Library (device, context, listener, channel pools), Channel (one OpenAL source), SoundBuffer (one OpenAL buffer), Listener (the ear — position and orientation), and a DeviceTracker that 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, calls SoundManager.playSoundEngine.play. Once per client tick Minecraft.tick calls SoundManager.tick, which walks the ticking sounds, updates positions and volumes, and expires finished channels; once per frame Minecraft.runTick calls SoundManager.updateSource with the camera so the listener moves smoothly. MusicManager.tick also runs here, choosing and fading background music.
  • Sound engine thread: a SoundEngineExecutor, which is a BlockableEventLoop — 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 through ChannelAccess. The Render thread never calls OpenAL itself.
  • Util.nonCriticalIoPool (the “Download-” threads): reads and decodes .ogg files (JOrbisAudioStream, a Java Vorbis decoder) into a SoundBuffer, inside SoundBufferLibrary.getCompleteBuffer; the result is a CompletableFuture that, 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:

  1. The server picks who hears it. ServerLevel.playSeededSound asks SoundEvent.getRange for the audible radius — a fixed range if the event declares one, otherwise 16 blocks scaled up by volumes above 1 — and PlayerList.broadcast sends ClientboundSoundPacket to 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.
  2. The client receives it on the game thread. ClientPacketListener.handleSoundEvent goes through PacketUtils.ensureRunningOnSameThread (see Anatomy) and calls ClientLevel.playSeededSound, which builds a SimpleSoundInstance and hands it to SoundManager.play.
  3. SoundEngine.play resolves the name to a file. The instance’s Identifier is looked up in the SoundManager registry to get a WeighedSoundEvents; WeighedSoundEvents.getSound rolls the weighted choice (following event-to-event redirects) to a concrete Sound; 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 registered SoundEventListener is told first — SubtitleOverlay is the only one, which is how subtitles work.
  4. A channel is borrowed on the sound thread. ChannelAccess.createHandle posts a task to the SoundEngineExecutor; on that thread Library acquires a Channel from the static or streaming pool (chosen by Sound.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.
  5. Parameters are set, then the data arrives later. The handle’s ChannelAccess.execute posts the pitch/volume/attenuation/position setup; separately, SoundBufferLibrary.getCompleteBuffer returns a future for the decoded buffer (cached per path; decoding runs on Util.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 what Sound.shouldPreload and SoundEngine.requestPreload are for.
  6. Streams are pumped by the tick. Long sounds (music, records) are streamed: Channel.attachBufferStream queues a few seconds of decoded audio, and ChannelAccess.scheduleTick, posted once per client tick from SoundEngine.tick, calls Channel.updateStream on each to refill. The same pass releases channels whose source reports stopped.
  7. The listener follows the camera. SoundEngine.updateSource posts a ListenerTransform (position, forward, up) from the Camera to the sound thread every frame; OpenAL does the distance attenuation and panning from there. SoundInstance.isRelative sounds (UI clicks) are positioned relative to the listener instead, so they never attenuate.

Interfaces

  • Called by: anything with a Level — blocks, entities, items — through Level.playSound; PlaySoundCommand; MusicManager for music; the ambient handlers in client/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 for sounds.json and the .ogg files.
  • Crosses the network as: ClientboundSoundPacket (a point in space), ClientboundSoundEntityPacket (attached to an entity, handled by ClientPacketListener.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), the Registries.SOUND_EVENT registry (static; data packs cannot add sound events, only reference them), and options.txt for category volumes, device, and HRTF.

Invariants and surprises

  • Your own sounds are predicted, not received. Player.playSound calls Level.playSound with itself as the except entity. On the server that broadcasts to everyone but you; on the client, ClientLevel.playSeededSound sees except == Minecraft.player and 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.playSound goes further and calls ClientLevel.playLocalSound directly.
  • The sound thread is an event loop, not a mixer. SoundEngineExecutor does 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. Library asks 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.play returns not started for a computed volume of 0 unless the instance says SoundInstance.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 a LoopingAudioStream, since the source only ever holds a few seconds.
  • Attenuation is linear and per-sound. SoundInstance.getAttenuation is linear or none; the distance is Sound.getAttenuationDistance (16 by default, from sounds.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.reload and SoundEngine.reload stop everything, tear down the OpenAL context in Library.cleanup, and SoundEngine.loadLibrary again. Changing the output device in options, or the DeviceTracker noticing 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.

#LectureSystem pages
1AnatomyAnatomy
2Sound — the smallest complete systemSound
(the rest of the map lives in docs/outline.md in the repo until each page exists)