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

Entity anatomy

Verified against Minecraft 26.3 · Part VI · What an entity is: one EntityType from the registry, through a factory, to a live object the level ticks.

You type /summon pig, and by the next tick there is a pig standing where you are. Between the command and the animal are three objects and one factory call: a ResourceKey in EntityTypeIds, the EntityType that EntityTypes built from it before any world existed, and the Entity that the type’s EntityType.EntityFactory returns. This page is the vocabulary of that chain, and the rest of Part VI is built on it. The surprise is what happens when the kind is one nobody has heard of, because that depends entirely on which of two doors it came through. An entity arriving over the network is named by a number, the registry index inside a ClientboundAddEntityPacket; an entity read back off disk is named by a string, the id field in the region file. Those two doors give opposite answers to the same bad name. Registries.ENTITY_TYPE is one of the few defaulted registries, its default is pig, and DefaultedMappedRegistry overrides nine methods, seven of which hand that default back — five for a miss, among them DefaultedMappedRegistry.byId and DefaultedMappedRegistry.getValue, and DefaultedMappedRegistry.getAny and DefaultedMappedRegistry.getDefaultKey always. So the numeric door cannot fail: ByteBufCodecs.registry decodes through IdMap.byIdOrThrow, which cannot throw here because DefaultedMappedRegistry.byId never returns null, and a pig walks out of the packet. The string door is not defaulted at all, because EntityType.CODEC is Registry.byNameCodec and resolves through a lookup the subclass leaves alone — as does DefaultedMappedRegistry.getOptional, the one override that deliberately answers empty. So the same unknown name in a region file yields nothing: EntityType.create logs Skipping Entity with id … and leaves a hole where the entity was. The default reaches the network and never reaches your save file.

The cast

classwhat it decidesthread
EntityTypeone registered kind: its factory, category, frozen dimensions, feature flags, and the two numbers that decide how it reaches clientsbuilt in a class initialiser, read from both game threads and the worldgen executor after
EntityTypeswhich 161 kinds exist and what every one of them is sized like — the most useful single table in the packageclass initialiser, once
Entityposition, box, network id, synched values, vehicle, removal reason. Deliberately thin on behaviourthe tick thread of whichever level owns it, or the worldgen executor before its chunk is live
EntityDimensionswidth, height, eye height, attachment points, and whether Attributes.SCALE may touch themimmutable record; the type’s is shared by every entity of the type
SynchedEntityDatawhich of the entity’s fields the other side is told about (synched entity data)one container per side; only the server’s writes travel
EntityInLevelCallbackwhether the object is in a world or merely on the heapinstalled by the level’s entity manager
ServerEntitywhen a tracked entity’s state becomes packets (what the client is told)the Server thread
EntityReferencehow one entity remembers another across a chunk unloadwherever it is resolved

Only one packet is this page’s own: ClientboundAddEntityPacket, the birth announcement. Everything after it — ClientboundSetEntityDataPacket, ClientboundEntityPositionSyncPacket, ClientboundEntityEventPacket, ClientboundRemoveEntitiesPacket — belongs to a sibling page.

The type is frozen, the entity is not

An entity is anything in the world that is not a block: mobs, players, items on the ground, arrows, boats, item frames, experience orbs, the invisible markers a data pack uses as bookmarks. The kind is an EntityType, built once and never changed. The individual is an Entity, and nearly all of its state is mutable by design.

classDiagram
    class EntityType {
        one object per registered kind, 161 of them
        EntityType.EntityFactory, usually the constructor
        MobCategory, the spawn cap and despawn distance
        clientTrackingRange in chunks, updateInterval in ticks
        frozen by EntityType.Builder.build
    }
    class EntityDimensions {
        width, height, eye height
        EntityAttachments, and a fixed flag
    }
    class Entity {
        SynchedEntityData, eight accessors and the chain's
        Entity.position at the feet, Entity.bb a stored box
        Entity.dimensions and Entity.eyeHeight, both caches
        Entity.levelCallback, a NULL sentinel until a level takes it
        Entity.removalReason, the whole am-I-alive bit
        Entity.vehicle and Entity.passengers
    }
    EntityType --> EntityDimensions : holds one, built once
    EntityType ..> Entity : EntityType.create calls the factory
    EntityDimensions ..> Entity : copied into the two caches, and recopied on a pose change

What each object holds, and the line the page turns on: everything in the top two boxes is frozen at class-initialiser time and shared by every individual of that kind, and everything in the bottom one belongs to one individual and changes.

Entity implements nine interfaces — Nameable, EntityAccess, ScoreHolder, SyncedDataHolder, DataComponentGetter, ItemOwner, SlotProvider, DebugValueSource and TypedInstance over EntityType — which is a fair measure of how many systems reach into it. Health, AI, the damage pipeline and inventory are all further down the tree.

In 26.3 the type constants are not on EntityType. They live in two parallel files: EntityTypeIds, 161 ResourceKeys with no reference to any entity class, and EntityTypes, the 161 matching objects that EntityType.Builder produced from them. MobCategory — MobCategory.MONSTER, MobCategory.CREATURE, MobCategory.AMBIENT, MobCategory.AXOLOTLS, MobCategory.UNDERGROUND_WATER_CREATURE, MobCategory.WATER_CREATURE, MobCategory.WATER_AMBIENT, MobCategory.MISC — carries the spawn cap (entity lifecycle) and the despawn distance (entity lifecycle). None of this is data-driven: Registries.ENTITY_TYPE is code-registered (the data-driven type pattern is what it is not), and what a data pack can reach is the tags in EntityTypeTags, the loot table at entities/<id>, DataComponents.ENTITY_DATA on a spawn egg, and the per-species variant registries (Registries.WOLF_VARIANT, Registries.CAT_VARIANT, Registries.PAINTING_VARIANT and the rest).

Dimensions, attachments and pose

EntityDimensions is a record — width, height, eye height, an EntityAttachments map and a fixed flag — and EntityDimensions.makeBoundingBox centres the box in X and Z on Entity.position and grows it upward, because that position is the bottom centre, the feet, and not the middle of the entity. EntityDimensions.scale returns the record unchanged when it is fixed, and also when both factors are 1. The flag is not the only way to escape Attributes.SCALE, either: plain Entity.getDimensions never scales at all, so every non-living entity ignores scale for free, and LivingEntity.getDimensions — which is final, the overridable hook being LivingEntity.getDefaultDimensions — short-circuits a sleeping entity to LivingEntity.SLEEPING_DIMENSIONS before any scaling happens.

EntityAttachments answers where a passenger sits, where the entity itself sits on a vehicle, where the name tag floats: EntityAttachment.PASSENGER, EntityAttachment.VEHICLE, EntityAttachment.NAME_TAG and EntityAttachment.WARDEN_CHEST, each with a fallback such as EntityAttachment.Fallback.AT_HEIGHT that EntityAttachments.Builder.build fills in from the width and height.

Pose is eighteen constants with explicit wire ids, not ordinals — the ones that matter are Pose.STANDING, Pose.CROUCHING, Pose.SWIMMING, Pose.FALL_FLYING, Pose.SLEEPING, Pose.SPIN_ATTACK and Pose.DYING, and the rest are animation states of one mob or a few, such as Pose.EMERGING and Pose.DIGGING. Pose.BY_ID is built with ByIdMap.OutOfBoundsStrategy.ZERO, so a pose id outside the range decodes silently to Pose.STANDING rather than failing the connection. Pose is the synched value on the base class that changes the box — a dozen subclasses have their own, from a pufferfish’s puff state to a slime’s size — and the loop is worth stating because everything else on this page hangs off it: Entity.setPose writes the value, SynchedEntityData.set calls Entity.onSyncedDataUpdated inside the setter, before anything is marked dirty, and that sees Entity.DATA_POSE and calls Entity.refreshDimensions, which asks Entity.getDimensions for the new record, overwrites both caches, and calls Entity.reapplyPosition to rebuild the box. The side that set the pose resizes immediately, the other resizes when the value lands. If the box grew, Entity.fudgePositionAfterSizeChange nudges the entity out of whatever it now overlaps — but only on the server, only after the first tick, only with physics on, only when the entity is not a Player, and only when the new box is at most four blocks in both width and height.

That loop is also the answer to why did the hitbox not change when I changed the size. Entity.dimensions, Entity.eyeHeight and Entity.bb are caches, and two things in the base classes refresh them unasked: a pose change, in Entity, and LivingEntity.onAttributeUpdated on Attributes.SCALE. The dozen subclasses with a box-changing value of their own — AgeableMob on the baby bit among them — each call Entity.refreshDimensions for it, and everything else has to call it by hand. While a cache is stale the two eye-height accessors disagree: Entity.getEyeHeight with no argument reads the cache, and the Pose overload recomputes.

The tree, and the class that was inserted into it

Entity has 18 direct subclasses and 192 descendants. LivingEntity and its own 124 make 125 of them — two thirds of the tree in one branch — and the seventeen non-living branches hold the other 67.

The class hierarchy under Entity: each branch shows how many types descend from it Entity (192) LivingEntity (124) Mob (114) PathfinderMob (108) AmbientCreature (1) 3 with no subclasses Avatar (7) Player (4) Mannequin (1) ArmorStand Projectile (26) AbstractHurtingProjectile (8) AbstractWindCharge (2) Fireball (2) 2 with no subclasses ThrowableProjectile (8) ThrowableItemProjectile (7) AbstractArrow (3) 3 with no subclasses 4 with no subclasses VehicleEntity (15) AbstractMinecart (8) AbstractMinecartContainer (2) 5 with no subclasses AbstractBoat (5) AbstractChestBoat (2) 2 with no subclasses BlockAttachedEntity (6) HangingEntity (3) ItemFrame (1) Painting 2 with no subclasses Display (3) 3 with no subclasses 13 with no subclasses
The Entity tree to three levels, generated from the decompile: a name in brackets carries how many types descend from it, and a grey line folds a node's childless children when there are two or more of them. Click to enlarge.

The full drawing, with the block, item and screen trees beside it, is in what extends what. What matters here is the shape: a long spine and a scattering. The spine is LivingEntity, and for all that it carries it has exactly three direct subclasses — Avatar, ArmorStand and Mob — which is worth saying plainly, because it means an armour stand is a living entity with no AI at all: no GoalSelector, no PathNavigation, both of those being Mob’s. The Brain is not Mob’s, though. It is declared on LivingEntity, built in its constructor and written under a Brain tag by every living entity, so an armour stand carries an empty one. PathfinderMob is a small class that scores walk targets and adds no way of moving, which is why Ghast and Phantom navigate without ever being one. AgeableMob adds babies, TamableAnimal an owner reference and a tame bit, Animal and Monster split by disposition, and Monster implements Enemy, a marker interface carrying nothing but XP-reward constants.

The 67 outside LivingEntity’s branch are shallow, and the atlas draws them as four families and a scattering: Projectile with 26 descendants, VehicleEntity with 15, BlockAttachedEntity with 6, Display with 3, and thirteen direct subclasses of Entity with no children of their own, from ItemEntity to LightningBolt. Sharing a base class that thin is what lets them disagree so completely about being hit (damage and death).

Avatar sits between LivingEntity and Player. It is a small class carrying the player-shaped Avatar.POSES dimension map, Avatar.DEFAULT_EYE_HEIGHT of 1.62 and one abstract method, Avatar.getProfile, so anything written against “Player extends LivingEntity” is wrong by one level. Its point is Mannequin, a posable, profile-skinned, player-looking entity in the decoration package that is not a Player and carries none of the inventory, abilities or hunger. What the rung holds and who draws it belong to player anatomy; what matters here is that the tree has that level.

Cutting across the tree are the capability interfaces, where most of the shared behaviour lives: Leashable is the fattest, with Leashable.tickLeash called from Entity.baseTick, and beside it Bucketable, EquipmentUser, NeutralMob, Attackable, Targeting, TraceableEntity, OwnableEntity, Shearable, PlayerRideableJumping, ItemSteerable, and ContainerUser, with exactly two implementors: Player and CopperGolem.

EntityReference deserves a name of its own. It stores either a UUID or the live object, and EntityReference.getEntity resolves lazily and decays back to the UUID the first time it is resolved after the target reports itself removed. It is how “who last hurt me”, “who owns this pet” and “who shot this arrow” survive a chunk unload.

Where the 727 files are

subpackagefileswhat
entity/ai277goals, brains, navigation, attributes, sensors
entity/animal130one subpackage per species or family now
entity/monster84mostly likewise
world/entity itself85Entity, LivingEntity, Mob, Avatar, EntityType, EntityTypes, the capability interfaces
entity/projectile37arrows, fireballs, thrown items
entity/boss24dragon and wither
entity/vehicle24boats and minecarts
entity/npc15villagers and traders
entity/player14Player, Inventory, Abilities
entity/decoration13armour stands, frames, paintings, Mannequin
entity/variant11the machinery that picks a data-driven mob variant
entity/item, entity/raid, entity/ambient, entity/schedule13items, raids, bats, and Activity — which is all entity/schedule still holds

From a registry entry to a live object

EntityTypes runs EntityType.Builder.build for each of the 161 keys in EntityTypeIds and registers the result, and it is that call which freezes the EntityDimensions and its attachment points for the life of the type. Everything below happens per entity, long afterwards.

sequenceDiagram
    participant SumC as SummonCommand
    participant ET as EntityType
    participant Entity as Entity
    participant SL as ServerLevel
    participant PESM as PersistentEntity<br/>SectionManager

    SumC->>SumC: rejects out of bounds, then on peaceful
    SumC->>ET: loadEntityRecursive, a tag with an id string
    ET->>ET: EntityType.by reads the id through EntityType.CODEC
    ET->>ET: EntityType.create checks canSpawn, then the factory
    ET->>Entity: the constructor: the next id, a new UUID, the type's dimensions
    Entity->>Entity: eight base accessors, then defineSynchedData, then setPos
    ET->>Entity: load, then readAdditionalSaveData
    SumC->>Entity: snapTo, the postLoad processor, before any passenger exists
    ET->>Entity: each Passengers child the same way, then startRiding
    SumC->>SL: tryAddFreshEntityWithPassengers, one body at a time
    SL->>PESM: addNewEntity
    PESM->>Entity: setLevelCallback, and only now is it in a world
    Note over SL,Entity: the next server tick
    SL->>Entity: commonTick, then tick

Thirteen steps between a registry entry and a live object, and the one that matters is the second from last: everything above Entity.setLevelCallback happens to an object no level knows about.

The client builds its own copy of the same object, from a packet, whenever a player comes into range — which is the same journey a second time, in five steps instead of thirteen:

sequenceDiagram
    participant Entity as Entity
    participant SE as ServerEntity
    participant CPL as ClientPacketListener

    SE->>Entity: getAddEntityPacket
    SE->>CPL: ClientboundAddEntityPacket, with data, attributes and equipment
    CPL->>CPL: createEntityFromPacket, a second object
    CPL->>CPL: Entity.recreateFrom<br/>Packet
    CPL->>CPL: ClientLevel.addEntity

The client’s Entity is not the server’s: it is a separate object, of the same class for all but a player or a mannequin, built from a packet rather than from a tag, and the two are only ever as alike as the channels in the rest of this part keep them.

Name to type. EntityType.by reads the id field through EntityType.CODEC. An unknown id yields nothing, and the entity is dropped with the log line the opening quoted — this is the save-file half of the hook.

Type to object. EntityType.create checks EntityType.canSpawn: the feature flags, plus a peaceful-difficulty test gated on the type’s own EntityType.isAllowedInPeaceful flag, which is a declared property and not a synonym for hostile. EntitySpawnRequest.ignoreChecks skips both. SummonCommand tests the same peaceful rule itself, a few lines earlier, purely to fail with a better message; the command does not set the skip flag, so both run. One type fails EntityType.create always: EntityTypes.PLAYER was built with EntityType.Builder.createNothing, so its factory returns null, which is why ClientPacketListener.createEntityFromPacket special-cases it and hand-builds a RemotePlayer from the player info it already has; it is EntityType.Builder.noSave and EntityType.Builder.noSummon besides. Otherwise the factory runs. The Entity constructor takes the next id from the level, invents a UUID with Mth.createInsecureUUID, copies the type’s dimensions into its cache, and builds the synched-data container — its own eight accessors, then the abstract Entity.defineSynchedData, which contributes nothing on the base class and exists only for the subclasses to chain up through (synched entity data names the eight and says why their order is the whole numbering scheme). It then calls Entity.setPos at the origin, so a fresh entity already has a full-size box rather than the zero-size Entity.INITIAL_AABB the field initialiser gave it.

Tag to state. Entity.load reads position (clamped to ±3.0000512E7 horizontally), motion, rotation and UUID, then calls the abstract Entity.readAdditionalSaveData, then Entity.reapplyPosition again if Entity.repositionEntityAfterLoad says so — which everything says except BlockAttachedEntity, the corpus’s one override, so paintings, item frames, leash knots and cushions are precisely the entities that skip it and keep the position their own load computed. The save twin is Entity.addAdditionalSaveData, and passengers save inside their vehicle: Entity.save returns false for anything currently riding, and the vehicle writes them into its Passengers list through Entity.saveAsPassenger, using Entity.getEncodeId, which is null for types that never serialise.

Object to level. ServerLevel.tryAddFreshEntityWithPassengers refuses if any UUID in the stack is already loaded, then ServerLevelAccessor.addFreshEntityWithPassengers calls ServerLevel.addFreshEntity once per body. PersistentEntitySectionManager.addNewEntity files it into a section and replaces EntityInLevelCallback.NULL with a real callback. That is the moment it stops being an object on the heap; entity lifecycle takes it from here.

Level to client. ServerEntity.addPairing sends ServerEntity.sendPairingData as one bundle: the packet Entity.getAddEntityPacket returns — id, UUID, type, position, velocity, three rotation bytes and one varint of type-specific data — then the non-default synched values, then attributes and equipment. On the client, ClientPacketListener.handleAddEntity builds the object, calls Entity.recreateFromPacket, and only then adds it to the level.

The spawn-egg path is a different pipeline that meets this one at the end. EntityType.spawn snaps the new entity out of collision with EntityType.getYOffset, aligns head and body rotation, runs Mob.finalizeSpawn, applies a PostSpawnProcessor, adds it through ServerLevelAccessor.addFreshEntityWithPassengers and plays an ambient sound. SummonCommand also calls Mob.finalizeSpawn, but only when the command asks for it, and it never touches the Y offset.

The tick both sides share

ServerLevel.tickNonPassenger opens a profiler section named after the entity type, calls Entity.commonTick — which counts Entity.invulnerableTime down, calls Entity.setOldPosAndRot, steps the interpolation on a client and increments Entity.tickCount — and then calls Entity.tick, all of it through Level.guardEntityTick, which turns any exception into a crash report with the entity’s details attached. ClientLevel.tickEntities does the same, through the same guard, having first skipped anything removed, riding or frozen by the tick-rate manager. Which entities the server’s loop reaches, and in what order, belongs to the level tick. No entity is ever ticked on a worker pool, and Entity is not thread-safe — though entities are constructed on one: ChunkStatus.SPAWN runs NaturalSpawner.spawnMobsForChunkGeneration on the worldgen executor, so a chunk’s first animals are built and finalised off the Server thread before the chunk ever becomes live.

Entity.tick on the base class is one line: call Entity.baseTick. Everything else readers remember happening “in tick” is in Entity.baseTick — the dead-vehicle check, the boarding cooldown, the portal handling, the fluid snapshot, swimming, fire ticking, the lava halving of fall distance, the below-world check, the leash — or in an override. LivingEntity.tick calls up and then runs LivingEntity.aiStep, and Mob.tick calls up and refreshes its goal-control flags through Mob.updateControlFlags every five ticks, on the server only — as the portal handling, the burning and the leash inside Entity.baseTick are too.

What differs between the sides is not the tick but what the tick is allowed to do, and that is exactly the subject of the next page, authority.

The id, and what compares equal

Entity.equals compares the network id and nothing else — not the UUID, not identity — and Entity.hashCode is the id. Ids are handed out by ServerLevel.getNextEntityId from a static AtomicInteger on ServerLevel, so they are process-global, which keeps two server levels’ ids apart, and the level is consulted only to avoid a collision. Equal ids still meet across levels: in singleplayer the client’s copy of an entity and the integrated server’s share one, which is why entities from two levels must never go in one set.

On the client there is no id at all until a packet supplies one. Level.getNextEntityId returns a literal zero and ClientLevel does not override it, while zero is the reserved invalid id and Entity.getId throws Tried to access entity ID before ID assignment on it. Between construction and the Entity.setId that Entity.recreateFromPacket performs, a client-side entity has no id, and therefore no equality and no hash. ServerLevel.getNextEntityId skips zero deliberately.

The two numbers frozen onto the type

The cast promised two numbers that decide how an entity reaches clients. EntityType.clientTrackingRange is in chunks and EntityType.updateInterval in ticks — 5 and 3 unless a builder says otherwise — and between them they decide who is told about the entity and how often its movement is sent, never mind what the packet says (what the client is told owns the asking, and a third parameter, EntityType.trackDeltas, which is true of every type but eleven). Thirty-eight of the 161 types set an interval explicitly, most of them at 10 or 20; eight set Integer.MAX_VALUE (most through EntityType.Builder.noUpdateInterval), which ChunkMap turns into UpdateInterval.NEVER, so their interval branch never fires at all — EntityTypes.ITEM_FRAME and EntityTypes.GLOW_ITEM_FRAME, EntityTypes.PAINTING, EntityTypes.LEASH_KNOT, EntityTypes.END_CRYSTAL, EntityTypes.LIGHTNING_BOLT, EntityTypes.AREA_EFFECT_CLOUD and EntityTypes.CUSHION. At the other end EntityTypes.MARKER has a tracking range of 0 and is never sent to anyone at all.

The client holds a lever over a distance of its own, how far away an entity is still drawn (in singleplayer the same option also scales how far the integrated server tracks), and it is process-global in the same way the ids are. Entity.viewScale is a static field on Entity, and LevelExtractor writes it from the effective render distance and the entity-distance option together, not from the option alone — which is why entity render distance moves when you touch a slider that does not name entities.

Where to look

Start at EntityTypes, which builds all 161 types and is the most useful single table in the package, and read EntityType.Builder.build beside it for what gets frozen. Then EntityType.create and EntityType.loadEntityRecursive for the chain this page traces, and Entity itself — its constructor, Entity.defineSynchedData and Entity.load — for what comes out of it. Entity.refreshDimensions is the way into the dimension caches and EntityDimensions.makeBoundingBox the arithmetic under them. PersistentEntitySectionManager.addNewEntity is where an object becomes part of a world. Two doors the page opens only a crack: EntityReference, for how one entity remembers another, and EntityType.spawn, the spawn-egg path that meets the trace at its end.


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