A block entity attaches persistent data and optional ticking logic to a block. Furnaces, chests, and command blocks all use them. This tutorial creates a simple ticking block entity that counts elapsed seconds and persists that value across world saves and server restarts.
Access Transformer & Class Tweaker
BlockEntityType.BlockEntitySupplier, the functional interface that BlockEntityType.Builder.of() accepts, is package-private in Minecraft's source. Any reference to it from mod code triggers an access error at compile time. The same AT and class tweaker pattern introduced in the Creating Items tutorial is used here: add one new entry to each file.
Add to common/src/main/resources/META-INF/accesstransformer.cfg:
Add to common/src/main/resources/examplemod.classtweaker:
Click Refresh Gradle after saving both files so the widening takes effect before your next build.
Block Entity Registry
Create a new class called BlockEntityRegistry in your common registry package:
The build(null) argument is the DataFixer type, which is null for all modded block entities that do not use the vanilla data-fixing system. Each block listed in Builder.of is one this block entity type can be attached to.
The Block Entity Class
Create ExampleBlockEntity in a new blockentity package in your common project:
The static tick method matches the signature required by BlockEntityTicker. Calling setChanged() marks the chunk dirty so the data is included in the next world save. Calling level.sendBlockUpdated queues a sync packet to nearby clients.
The Block Class
Blocks that own a block entity must extend BaseEntityBlock (which implements the EntityBlock interface). BaseEntityBlock declares codec() as abstract, so every subclass must provide a MapCodec. You also need to override getRenderShape to restore solid model rendering, since BaseEntityBlock returns RenderShape.INVISIBLE by default:
simpleCodec helper builds the required MapCodec from any constructor that takes only a Properties argument. If your block constructor takes additional parameters you will need to write the codec manually using RecordCodecBuilder. The getRenderShape override is required: without it the block model is invisible.Saving and Loading Data
Override saveAdditional and loadAdditional in ExampleBlockEntity to persist custom fields to NBT:
Always call super first so the base class can save and load its own fields such as the block entity type and position. Use unique NBT key names to avoid collisions if you extend this class later.
Syncing to the Client
Add getUpdateTag and getUpdatePacket so the block entity state reaches connected clients when a chunk loads or when sendBlockUpdated is called:
saveWithoutMetadata serialises all of the block entity's custom data without including the type ID or position, keeping the packet small. The client calls loadAdditional when it receives the packet, so no extra handling is needed.
Wiring Up
Register the block entity block in BlockRegistry:
Then call BlockEntityRegistry.init() from CommonClass.init() after the block registry so all blocks exist before the block entity type is built:
Datagen and Testing
Add the new block to your existing datagen providers:
- ExampleModelProvider: call
blockModels.createTrivialCube(BlockRegistry.EXAMPLE_BE_BLOCK.get())andblockModels.registerSimpleItemModel(BlockRegistry.EXAMPLE_BE_BLOCK.get(), ModelLocationUtils.getModelLocation(BlockRegistry.EXAMPLE_BE_BLOCK.get())) - ExampleBlockLootTableProvider:
dropSelf(BlockRegistry.EXAMPLE_BE_BLOCK.get()) - ExampleBlockTagsProvider: add to
MINEABLE_WITH_PICKAXE - ExampleLanguageProvider:
add(BlockRegistry.EXAMPLE_BE_BLOCK.get(), "Example Block Entity Block")
Place the block in a creative-mode world and switch to survival. Use /data get block ~ ~ ~ SecondsAlive at the block's position to confirm the counter is incrementing and persisting correctly across chunk reloads. Break and replace the block to verify the counter resets to zero, confirming the block entity is recreated on placement.
Container or extend BaseContainerBlockEntity and pair it with a MenuType registration and a screen class. That pattern is covered in a later tutorial on custom menus.You can find the source for this tutorial here:
View Source on GitHub