Block Basics
TheBlock interface represents an immutable block that can be placed in the world.
Getting and Setting Blocks
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.block.Block;
import net.minestom.server.coordinate.Pos;
// Get block at position
Block block = instance.getBlock(10, 40, 10);
// Set block at position
instance.setBlock(10, 40, 10, Block.STONE);
// Set block with position object
Pos pos = new Pos(10, 40, 10);
instance.setBlock(pos, Block.DIAMOND_BLOCK);
Block Properties
Blocks can have properties that define their state:// Get a property
String facing = block.getProperty("facing");
String open = block.getProperty("open");
// Set a property (returns new block)
Block newBlock = block.withProperty("facing", "north");
Block openDoor = block.withProperty("open", "true");
// Set multiple properties
Map<String, String> properties = Map.of(
"facing", "south",
"open", "true",
"half", "upper"
);
Block configured = block.withProperties(properties);
instance.setBlock(pos, configured);
Blocks are immutable. Methods like
withProperty() return a new Block instance.Block States
// Get block state ID
int stateId = block.stateId();
// Create block from state
Block fromState = Block.fromState("minecraft:chest[facing=north,type=single]");
// Get full state string
String state = block.state();
Block Handlers
BlockHandler allows you to add custom behavior to blocks. Handlers are called when blocks are placed, destroyed, or interacted with.
Creating a Block Handler
import net.kyori.adventure.key.Key;
import net.minestom.server.instance.block.BlockHandler;
public class TestBlockHandler implements BlockHandler {
@Override
public Key getKey() {
return Key.key("minestom", "test");
}
@Override
public void onPlace(Placement placement) {
System.out.println("Block placed at: " + placement.getBlockPosition());
System.out.println("Previous block: " + placement.getPreviousBlock());
}
@Override
public void onDestroy(Destroy destroy) {
System.out.println("Block destroyed at: " + destroy.getBlockPosition());
System.out.println("Replaced with: " + destroy.getNewBlock());
}
@Override
public boolean onInteract(Interaction interaction) {
Player player = interaction.getPlayer();
player.sendMessage("You clicked the custom block!");
return true; // Return false to cancel interaction
}
}
Registering Block Handlers
import net.minestom.server.MinecraftServer;
import net.minestom.server.instance.block.BlockManager;
BlockManager blockManager = MinecraftServer.getBlockManager();
// Register handler instance
TestBlockHandler handler = new TestBlockHandler();
blockManager.registerHandler(handler.getKey(), () -> handler);
// Register for specific blocks
blockManager.registerHandler("minecraft:diamond_block", () -> handler);
Applying Handlers to Blocks
// Attach handler when placing block
Block block = Block.STONE.withHandler(handler);
instance.setBlock(pos, block);
// Or attach during placement event
eventNode.addListener(PlayerBlockPlaceEvent.class, event -> {
Block block = event.getBlock();
BlockHandler handler = block.handler();
if (handler == null) {
// Get handler from BlockManager
handler = MinecraftServer.getBlockManager()
.getHandler(block.key().asString());
event.setBlock(block.withHandler(handler));
}
});
Advanced Block Handler Features
Tickable Blocks
Create blocks that update over time:public class TickingBlockHandler implements BlockHandler {
@Override
public Key getKey() {
return Key.key("minestom", "ticking_block");
}
@Override
public boolean isTickable() {
return true;
}
@Override
public void tick(Tick tick) {
Block block = tick.getBlock();
Instance instance = tick.getInstance();
Point pos = tick.getBlockPosition();
// Update block state or perform actions
System.out.println("Ticking at: " + pos);
}
}
Entity Touch Detection
@Override
public void onTouch(Touch touch) {
Entity touching = touch.getTouching();
Point blockPos = touch.getBlockPosition();
if (touching instanceof Player player) {
player.sendMessage("You touched a special block!");
}
}
Block Entity Tags
import net.minestom.server.tag.Tag;
@Override
public Collection<Tag<?>> getBlockEntityTags() {
return List.of(
Tag.String("CustomData"),
Tag.Integer("Energy")
);
}
@Override
public byte getBlockEntityAction() {
return 1; // Action ID for block entity
}
Block Placement Rules
Placement rules control how blocks can be placed in the world.Creating a Placement Rule
import net.minestom.server.instance.block.BlockFace;
import net.minestom.server.instance.block.rule.BlockPlacementRule;
public class DripstonePlacementRule extends BlockPlacementRule {
public DripstonePlacementRule() {
super(Block.POINTED_DRIPSTONE);
}
@Override
public Block blockPlace(PlacementState state) {
BlockFace face = state.blockFace();
// Set vertical direction based on placement face
if (face == BlockFace.TOP) {
return state.block().withProperty("vertical_direction", "up");
} else if (face == BlockFace.BOTTOM) {
return state.block().withProperty("vertical_direction", "down");
}
return state.block();
}
}
Bed Placement Rule Example
From the Minestom demo - shows complex multi-block placement:public class BedPlacementRule extends BlockPlacementRule {
public BedPlacementRule(Block block) {
super(block);
}
@Override
public Block blockPlace(PlacementState placementState) {
Player player = placementState.player();
if (player == null) return placementState.block();
BlockFace playerFacing = player.getPosition().direction().toBlockFace();
Block placedBlock = placementState.block()
.withProperty("facing", playerFacing.name().toLowerCase())
.withProperty("part", "foot");
// Place head part
Point headPos = placementState.placePosition()
.add(playerFacing.toDirection().vec().asPos());
Block headBlock = placementState.block()
.withProperty("facing", playerFacing.name().toLowerCase())
.withProperty("part", "head");
placementState.instance().setBlock(headPos, headBlock);
return placedBlock;
}
}
Registering Placement Rules
BlockManager blockManager = MinecraftServer.getBlockManager();
// Register for single block
blockManager.registerBlockPlacementRule(new DripstonePlacementRule());
// Register for multiple blocks
var beds = Block.values().stream()
.filter(block -> BlockEntityType.BED.equals(block.registry().blockEntityType()))
.toList();
beds.forEach(block ->
blockManager.registerBlockPlacementRule(new BedPlacementRule(block))
);
Block Events
Handle block-related events:import net.minestom.server.event.player.*;
// Block placement
eventNode.addListener(PlayerBlockPlaceEvent.class, event -> {
Player player = event.getPlayer();
Block block = event.getBlock();
Point pos = event.getBlockPosition();
player.sendMessage("Placed " + block.key().asString());
});
// Block breaking
eventNode.addListener(PlayerBlockBreakEvent.class, event -> {
Block block = event.getBlock();
Point pos = event.getBlockPosition();
// Handle multi-block structures (like beds)
if (block.getProperty("part") != null) {
// Break both parts
handleMultiBlockBreak(event);
}
});
// Block interaction
eventNode.addListener(PlayerBlockInteractEvent.class, event -> {
Block block = event.getBlock();
Player player = event.getPlayer();
// Toggle door
String open = block.getProperty("open");
if (open != null) {
block = block.withProperty("open",
String.valueOf(!Boolean.parseBoolean(open)));
event.getInstance().setBlock(event.getBlockPosition(), block);
}
});
Block Tags and NBT
Using Tags
import net.minestom.server.tag.Tag;
// Create tag
Tag<String> customTag = Tag.String("custom_data");
// Set tag on block
Block block = Block.CHEST.withTag(customTag, "my data");
instance.setBlock(pos, block);
// Read tag
String data = block.getTag(customTag);
NBT Data
import net.kyori.adventure.nbt.CompoundBinaryTag;
// Create NBT data
CompoundBinaryTag nbt = CompoundBinaryTag.builder()
.putString("CustomName", "Special Chest")
.putInt("Items", 5)
.build();
// Apply to block
Block block = Block.CHEST.withNbt(nbt);
instance.setBlock(pos, block);
// Read NBT
CompoundBinaryTag blockNbt = block.nbt();
Complete Example: Sign Handler
Here’s the sign handler from the Minestom demo:import net.kyori.adventure.key.Key;
import net.minestom.server.instance.block.BlockHandler;
public class SignHandler implements BlockHandler {
@Override
public Key getKey() {
return Key.key("minestom", "sign");
}
@Override
public void onPlace(Placement placement) {
System.out.println("Sign placed at: " + placement.getBlockPosition());
}
@Override
public void onDestroy(Destroy destroy) {
System.out.println("Sign destroyed at: " + destroy.getBlockPosition());
}
}
// Register for all sign types
BlockManager blockManager = MinecraftServer.getBlockManager();
RegistryTag<Block> signTag = Block.staticRegistry()
.getTag(TagKey.ofHash("#minecraft:all_signs"));
SignHandler signHandler = new SignHandler();
for (RegistryKey<Block> key : signTag) {
blockManager.registerHandler(key.key(), () -> signHandler);
}
Next Steps
World Generation
Generate terrain with custom blocks
Commands
Create commands to manipulate blocks
