Blocks and block data
Read and change blocks, from simple stone to stairs, crops and chests.
The world is made of blocks, and a lot of plugins are about changing them: replanting crops, felling trees, protecting builds, filling chests, writing signs. On this page you will learn the four words that confuse every beginner (Block, Material, BlockData and BlockState), how to find, read, change and break blocks, and how to work with stairs, crops, chests and signs. You will finish with a plugin that replants crops and chops down whole trees.
Four words that mean different things
Paper has four types that all sound like "a block". Each answers a different question, and mixing them up is the number one block bug. Here they are, with a stair block sitting at a spot in your world as the example:
- Block: one spot in the world, like the cell at x 10, y 64, z -3. It always shows what is there right now. If a player breaks it a second later, your
Blockobject shows air. - Material: what kind it is, such as
OAK_STAIRS. It is a fixed list of every block and item type in the game, like the creative inventory. - BlockData: the settings of the block. For stairs: which way they face, whether they are upside down, whether they hold water. In the F3 debug screen these show up as
facing: eastandhalf: topunder "Targeted Block". - BlockState: a snapshot copy of the block including the stuff inside it, such as the items in a chest, the lines on a sign or the mob a spawner spawns. You change the copy and then call
update().
A simple rule decides which one you need: if the thing you want to change is a setting (age, direction, open or closed), use BlockData. If it is content inside the block (items, text), use BlockState. If you only care what kind of block it is, a Material is enough.
Getting a block
You rarely find a block from nothing. Usually one is handed to you, or you start from a world, a location or another block:
Where this file livesblocks-snippetssrcmainjavacomexampleblockssnippetsBlockSnippets.java
The package com.example.blockssnippets is the folder path com/example/blockssnippets inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- blocks-snippets/
- src/main/java/com/example/blockssnippets/Package com.example.blockssnippets
- BlockEventSnippets.javaListener: reacts to events
- BlockSnippets.javayou are hereHelper class
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/java/com/example/blockssnippets/Package com.example.blockssnippets
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
27void gettingBlocks(World world, Player player, Location location) {28 Block fromWorld = world.getBlockAt(10, 64, -3);29 Block fromLocation = location.getBlock();30 Block feet = player.getLocation().getBlock();31 Block underFeet = feet.getRelative(BlockFace.DOWN);32 Block twoAbove = underFeet.getRelative(BlockFace.UP, 2);33 Block diagonal = underFeet.getRelative(1, 0, -1);34}- The block at exact whole-number coordinates. These are block coordinates, as explained in Worlds, locations and movement.
- The block that a
Locationis inside. Works for player locations, entity locations and any location you built. - The neighbor on one side. Here: the block under the player's feet.
- The neighbor two steps away in that direction.
- A neighbor by offsets in x, y and z, handy for diagonals. Here it is one block east and one block north.
Events that are about blocks give you the block directly, for example BlockBreakEvent#getBlock(). A block can also give you its getLocation() and getWorld() back.
Reading a block
Where this file livesblocks-snippetssrcmainjavacomexampleblockssnippetsBlockSnippets.java
The package com.example.blockssnippets is the folder path com/example/blockssnippets inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- blocks-snippets/
- src/main/java/com/example/blockssnippets/Package com.example.blockssnippets
- BlockEventSnippets.javaListener: reacts to events
- BlockSnippets.javayou are hereHelper class
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/java/com/example/blockssnippets/Package com.example.blockssnippets
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
36void reading(Block block) {37 Material type = block.getType();38 boolean isAir = block.isEmpty();39 boolean isSolid = block.isSolid();40 Location where = block.getLocation();41}- The
Material, such asSTONEorOAK_LOG. Compare it with==:block.getType() == Material.STONE. - True for air. Minecraft has three kinds of air (normal, cave and void). To be sure you catch all of them, you can also ask the material:
block.getType().isAir(). - True for blocks you collide with, such as stone. False for air, water, torches and flowers.
Changing a block
The simplest change is setType. It replaces the block with another kind, in its default settings:
Where this file livesblocks-snippetssrcmainjavacomexampleblockssnippetsBlockSnippets.java
The package com.example.blockssnippets is the folder path com/example/blockssnippets inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- blocks-snippets/
- src/main/java/com/example/blockssnippets/Package com.example.blockssnippets
- BlockEventSnippets.javaListener: reacts to events
- BlockSnippets.javayou are hereHelper class
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/java/com/example/blockssnippets/Package com.example.blockssnippets
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
43void changing(Block block) {44 block.setType(Material.GOLD_BLOCK);45 block.setType(Material.STONE, false);46 block.setType(Material.AIR);47}- Turns the block into gold. Whatever it was before is gone, and nothing drops.
- The second argument is
applyPhysics.falsetells the game not to notify the neighbors about the change. - Removing a block means replacing it with air. No drops, no sound, no particles.
Physics is the game reacting to a block change: sand falls when the block under it vanishes, torches pop off when their wall is removed, water starts to flow. setType(material) keeps physics on, which is what you want almost always. Only turn it off for special cases, such as building a two-block-tall door yourself, where the first half would pop off before you place the second.
Breaking a block like a player
Replacing a block with air makes it disappear, but a player who breaks a block also gets items. For that, use breakNaturally. It breaks the block, drops the right items and plays the break effect. Pass the tool so enchantments such as Fortune and Silk Touch count:
Where this file livesblocks-snippetssrcmainjavacomexampleblockssnippetsBlockSnippets.java
The package com.example.blockssnippets is the folder path com/example/blockssnippets inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- blocks-snippets/
- src/main/java/com/example/blockssnippets/Package com.example.blockssnippets
- BlockEventSnippets.javaListener: reacts to events
- BlockSnippets.javayou are hereHelper class
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/java/com/example/blockssnippets/Package com.example.blockssnippets
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
49void breaking(Block block, Player player) {50 ItemStack tool = player.getInventory().getItemInMainHand();51 Collection<ItemStack> drops = block.getDrops(tool);52 block.breakNaturally(tool);53}- The item the player holds. It works as the tool for the break.
- Only asks what would drop and returns a list. The block stays where it is.
- Breaks the block and lets the drops fall as items on the ground.
| You want | Use |
|---|---|
| Remove the block silently | block.setType(Material.AIR) |
| Break it with normal drops and effects | block.breakNaturally(tool) |
| Know the drops without breaking | block.getDrops(tool) |
| Stop a player from breaking it | event.setCancelled(true) in a BlockBreakEvent |
BlockData: the settings of a block
BlockData describes the settings of one block. Different blocks have different settings, so BlockData comes in many specialized versions. You ask "is this block one of those?" with instanceof, and Java gives you the specialized object at the same time. You first met that style in Commands, part 1.
Crops: Ageable
Wheat, carrots, potatoes and many other plants grow through ages, from 0 (freshly planted) to a maximum (ripe). They all share the Ageable interface:
Where this file livesblocks-snippetssrcmainjavacomexampleblockssnippetsBlockSnippets.java
The package com.example.blockssnippets is the folder path com/example/blockssnippets inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- blocks-snippets/
- src/main/java/com/example/blockssnippets/Package com.example.blockssnippets
- BlockEventSnippets.javaListener: reacts to events
- BlockSnippets.javayou are hereHelper class
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/java/com/example/blockssnippets/Package com.example.blockssnippets
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
55void ripeCrop(Block block) {56 if (block.getBlockData() instanceof Ageable crop) {57 crop.setAge(crop.getMaximumAge());58 block.setBlockData(crop);59 }60}- "If this block's data is an
Ageable, call itcrop." Blocks that are not plants skip the wholeif. - The age at which this plant is ripe. It is 7 for wheat, but only 3 for beetroots, so never hard-code the number.
- Without this line, nothing happens.
getBlockData()gives you a copy. Changing the copy does not touch the world until you hand it back.
Facing and half: stairs
Stairs have several settings, from three small interfaces: Directional (which way they face), Bisected (bottom or top half) and Waterlogged. To place a block with exact settings, create its data first, adjust it and apply it:
Where this file livesblocks-snippetssrcmainjavacomexampleblockssnippetsBlockSnippets.java
The package com.example.blockssnippets is the folder path com/example/blockssnippets inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- blocks-snippets/
- src/main/java/com/example/blockssnippets/Package com.example.blockssnippets
- BlockEventSnippets.javaListener: reacts to events
- BlockSnippets.javayou are hereHelper class
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/java/com/example/blockssnippets/Package com.example.blockssnippets
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
62void buildStairs(Block block) {63 Stairs stairs = (Stairs) Material.OAK_STAIRS.createBlockData();64 stairs.setFacing(BlockFace.EAST);65 stairs.setHalf(Bisected.Half.TOP);66 block.setBlockData(stairs);67}- Creates the default data for oak stairs. It returns the general
BlockDatatype. - A
cast : "I know this is theStairskind of data." It is safe here because we just created data for stairs. For data you did not create yourself, useinstanceofinstead. - The direction the stairs face. Only horizontal faces are valid.
- Upside-down stairs, attached to the top of the block space.
You can also describe block data as text, the same way the /setblock command does. This is short, and a good choice when the settings are fixed:
Where this file livesblocks-snippetssrcmainjavacomexampleblockssnippetsBlockSnippets.java
The package com.example.blockssnippets is the folder path com/example/blockssnippets inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- blocks-snippets/
- src/main/java/com/example/blockssnippets/Package com.example.blockssnippets
- BlockEventSnippets.javaListener: reacts to events
- BlockSnippets.javayou are hereHelper class
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/java/com/example/blockssnippets/Package com.example.blockssnippets
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
69void stairsFromText() {70 BlockData stairs = Bukkit.createBlockData("minecraft:oak_stairs[facing=east,half=top]");71}- Parses text like
minecraft:oak_stairs[facing=east,half=top]. It throws anIllegalArgumentExceptionif the text is not valid, so a typo shows up the first time the code runs.
Water in blocks: Waterlogged
Where this file livesblocks-snippetssrcmainjavacomexampleblockssnippetsBlockSnippets.java
The package com.example.blockssnippets is the folder path com/example/blockssnippets inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- blocks-snippets/
- src/main/java/com/example/blockssnippets/Package com.example.blockssnippets
- BlockEventSnippets.javaListener: reacts to events
- BlockSnippets.javayou are hereHelper class
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/java/com/example/blockssnippets/Package com.example.blockssnippets
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
73void waterlog(Block block) {74 if (block.getBlockData() instanceof Waterlogged waterlogged) {75 waterlogged.setWaterlogged(true);76 block.setBlockData(waterlogged);77 }78}- Only some blocks can hold water (stairs, slabs, fences, trapdoors). Others are not
Waterlogged, so they skip this code. - Fills the block with water, like using a water bucket on a slab.
Other settings follow the same pattern: Openable for doors and trapdoors, Powerable for levers and plates, Levelled for water and cauldrons, Lightable for furnaces and campfires. IntelliJ's autocomplete after block.getBlockData() instanceof shows them all.
BlockState: what is inside a block
A chest holds items, a sign holds text, a spawner holds a mob type. That kind of content cannot be described with BlockData, so Paper uses BlockState. Blocks that carry data like this are called tile entities and their states extend TileState.
The workflow has three steps: get the state, change it, and (usually) call update() to write your changes back.
Where this file livesblocks-snippetssrcmainjavacomexampleblockssnippetsBlockSnippets.java
The package com.example.blockssnippets is the folder path com/example/blockssnippets inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- blocks-snippets/
- src/main/java/com/example/blockssnippets/Package com.example.blockssnippets
- BlockEventSnippets.javaListener: reacts to events
- BlockSnippets.javayou are hereHelper class
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/java/com/example/blockssnippets/Package com.example.blockssnippets
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
86void writeSign(Block block) {87 if (block.getState() instanceof Sign sign) {88 sign.getSide(Side.FRONT).line(0, Component.text("Welcome!"));89 sign.update();90 }91}- Takes a
snapshot of the block. If it is a sign, you get aSignobject for the copy. If the block is anything else, the code is skipped. - Signs have two writable sides now, so you choose
FRONTorBACK. The oldsign.setLinemethods are deprecated for this reason. - Writes the first of the four lines. Lines are numbered from 0, and the text is a Component.
- Writes the snapshot back to the real block. Without it, the sign stays blank.
Chests are a pleasant exception. The inventory you get from a chest state is the real inventory, so changes to it happen at once and no update() is needed:
Where this file livesblocks-snippetssrcmainjavacomexampleblockssnippetsBlockSnippets.java
The package com.example.blockssnippets is the folder path com/example/blockssnippets inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- blocks-snippets/
- src/main/java/com/example/blockssnippets/Package com.example.blockssnippets
- BlockEventSnippets.javaListener: reacts to events
- BlockSnippets.javayou are hereHelper class
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/java/com/example/blockssnippets/Package com.example.blockssnippets
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
80void fillChest(Block block) {81 if (block.getState() instanceof Chest chest) {82 chest.getInventory().addItem(ItemStack.of(Material.DIAMOND, 3));83 }84}- Furnaces, barrels, hoppers and shulker boxes work the same way through
Container, the shared parent type. - Puts three diamonds into the first free slot, just like a player would. Items and ItemStacks and Inventories go deeper.
Spawners are the same story as signs (change the snapshot, then update()):
Where this file livesblocks-snippetssrcmainjavacomexampleblockssnippetsBlockSnippets.java
The package com.example.blockssnippets is the folder path com/example/blockssnippets inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- blocks-snippets/
- src/main/java/com/example/blockssnippets/Package com.example.blockssnippets
- BlockEventSnippets.javaListener: reacts to events
- BlockSnippets.javayou are hereHelper class
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/java/com/example/blockssnippets/Package com.example.blockssnippets
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
93void changeSpawner(Block block) {94 if (block.getState() instanceof CreatureSpawner spawner) {95 spawner.setSpawnedType(EntityType.ZOMBIE);96 spawner.update();97 }98}- The state type for monster spawners.
- The spawner now makes zombies.
- Required, because the spawner state is a snapshot.
Tags: groups of blocks
Often you do not care about one block type but about a whole group: any kind of log, any kind of leaves, any block you mine with a pickaxe. Minecraft has those groups built in and calls them tags. Without tags you would write a long list and forget the new wood types that Mojang adds.
Where this file livesblocks-snippetssrcmainjavacomexampleblockssnippetsBlockSnippets.java
The package com.example.blockssnippets is the folder path com/example/blockssnippets inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- blocks-snippets/
- src/main/java/com/example/blockssnippets/Package com.example.blockssnippets
- BlockEventSnippets.javaListener: reacts to events
- BlockSnippets.javayou are hereHelper class
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/java/com/example/blockssnippets/Package com.example.blockssnippets
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
100void tags(Block block) {101 boolean isLog = Tag.LOGS.isTagged(block.getType());102 boolean needsPickaxe = Tag.MINEABLE_PICKAXE.isTagged(block.getType());103}- True for oak, birch, spruce and every other log, including stripped logs and any wood type added in the future.
- The blocks that a pickaxe breaks efficiently. There are tags for crops, leaves, beds, doors, stairs, flowers and many more. Type
Tag.and read the autocomplete list.
Block events: the recap
You met BlockBreakEvent in Events and listeners. Here is the full picture for the two events you will use most:
Where this file livesblocks-snippetssrcmainjavacomexampleblockssnippetsBlockEventSnippets.java
The package com.example.blockssnippets is the folder path com/example/blockssnippets inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- blocks-snippets/
- src/main/java/com/example/blockssnippets/Package com.example.blockssnippets
- BlockEventSnippets.javayou are hereListener: reacts to events
- BlockSnippets.javaHelper class
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/java/com/example/blockssnippets/Package com.example.blockssnippets
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
1package com.example.blockssnippets;2 3import org.bukkit.Material;4import org.bukkit.block.Block;5import org.bukkit.event.EventHandler;6import org.bukkit.event.Listener;7import org.bukkit.event.block.BlockBreakEvent;8import org.bukkit.event.block.BlockPlaceEvent;9import org.bukkit.inventory.ItemStack;10 11final class BlockEventSnippets implements Listener {12 13 @EventHandler14 public void onPlace(BlockPlaceEvent event) {15 Block placed = event.getBlockPlaced();16 Block against = event.getBlockAgainst();17 ItemStack inHand = event.getItemInHand();18 if (placed.getType() == Material.TNT) {19 event.setCancelled(true);20 }21 }22 23 @EventHandler24 public void onBreak(BlockBreakEvent event) {25 Block broken = event.getBlock();26 event.setDropItems(false);27 event.setExpToDrop(0);28 }29}- The new block, already in the world when the event fires.
- The block the player clicked on to place this one, such as the ground.
- The item the player placed from.
- Stops the placement. Here, nobody can place TNT.
- The block that is about to break. It is still there while the event runs.
- The block breaks, but no items drop.
- No experience orbs either.
Demo plugin: auto-replant and tree feller
Two small listeners show everything from this page working together.
Auto-replant
Break a fully grown crop and it grows back from age 0. Young crops break normally. The plan is: skip everything that is not a ripe crop, then break it ourselves and place a fresh one.
Where this file livesblocks-demosrcmainjavacomexampleblocksdemoAutoReplantListener.java
The package com.example.blocksdemo is the folder path com/example/blocksdemo inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- blocks-demo/
- src/main/
- java/com/example/blocksdemo/Package com.example.blocksdemo
- AutoReplantListener.javayou are hereListener: reacts to events
- BlocksDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- TreeFellerListener.javaListener: reacts to events
- resources/Files copied into the jar as they are
- plugin.ymlTells Paper the plugin's name, version and main class
- java/com/example/blocksdemo/Package com.example.blocksdemo
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
1package com.example.blocksdemo;2 3import org.bukkit.GameMode;4import org.bukkit.Material;5import org.bukkit.Tag;6import org.bukkit.block.Block;7import org.bukkit.block.data.Ageable;8import org.bukkit.entity.Player;9import org.bukkit.event.EventHandler;10import org.bukkit.event.Listener;11import org.bukkit.event.block.BlockBreakEvent;12import org.bukkit.inventory.ItemStack;13 14public final class AutoReplantListener implements Listener {15 16 @EventHandler(ignoreCancelled = true)17 public void onBreak(BlockBreakEvent event) {18 Block block = event.getBlock();19 if (!Tag.CROPS.isTagged(block.getType())) {20 return;21 }22 if (!(block.getBlockData() instanceof Ageable crop) || crop.getAge() < crop.getMaximumAge()) {23 return;24 }25 26 Player player = event.getPlayer();27 if (player.getGameMode() == GameMode.CREATIVE) {28 return;29 }30 31 Material cropType = block.getType();32 ItemStack tool = player.getInventory().getItemInMainHand();33 event.setCancelled(true);34 block.breakNaturally(tool);35 block.setType(cropType);36 }37}- If another plugin (such as a land protection plugin) already canceled this break, our handler stays out of it. Event priorities and canceling explains this option.
- Wheat, carrots, potatoes, beetroots and the other crops, without listing them.
- "Leave if this is not an
Ageable, or if it is not ripe yet." The second half only runs when the first half was false, socropis safe to use there. - In creative mode players should not get drops, so we let the normal break happen.
- Remembers which crop it was, because the block turns into air in a moment.
- We cancel the normal break and do the work ourselves, so we control what happens to the block afterwards.
- Breaks the ripe crop with normal drops (and Fortune from the tool).
- Plants the same crop again. Its data starts at age 0, so it begins to grow.
Tree feller
Chop a log with an axe and the whole tree comes down. Hold Shift to break just one log. The listener has to find the logs that are connected to the first one. It does that with a classic technique: start at one block, look at all its neighbors, then at the neighbors of those, and keep going until there are no more.
Where this file livesblocks-demosrcmainjavacomexampleblocksdemoTreeFellerListener.java
The package com.example.blocksdemo is the folder path com/example/blocksdemo inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- blocks-demo/
- src/main/
- java/com/example/blocksdemo/Package com.example.blocksdemo
- AutoReplantListener.javaListener: reacts to events
- BlocksDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- TreeFellerListener.javayou are hereListener: reacts to events
- resources/Files copied into the jar as they are
- plugin.ymlTells Paper the plugin's name, version and main class
- java/com/example/blocksdemo/Package com.example.blocksdemo
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
1package com.example.blocksdemo;2 3import java.util.ArrayDeque;4import java.util.ArrayList;5import java.util.Deque;6import java.util.HashSet;7import java.util.List;8import java.util.Set;9import org.bukkit.GameMode;10import org.bukkit.Tag;11import org.bukkit.block.Block;12import org.bukkit.entity.Player;13import org.bukkit.event.EventHandler;14import org.bukkit.event.Listener;15import org.bukkit.event.block.BlockBreakEvent;16import org.bukkit.inventory.EquipmentSlot;17import org.bukkit.inventory.ItemStack;18 19public final class TreeFellerListener implements Listener {20 21 private static final int MAX_LOGS = 64;22 23 @EventHandler(ignoreCancelled = true)24 public void onBreak(BlockBreakEvent event) {25 Player player = event.getPlayer();26 Block firstLog = event.getBlock();27 ItemStack axe = player.getInventory().getItemInMainHand();28 29 if (!Tag.LOGS.isTagged(firstLog.getType()) || !Tag.ITEMS_AXES.isTagged(axe.getType())) {30 return;31 }32 if (player.isSneaking() || player.getGameMode() == GameMode.CREATIVE) {33 return;34 }35 36 for (Block log : findConnectedLogs(firstLog)) {37 ItemStack tool = player.getInventory().getItemInMainHand();38 if (tool.isEmpty()) {39 break;40 }41 log.breakNaturally(tool);42 player.damageItemStack(EquipmentSlot.HAND, 1);43 }44 }45 46 private List<Block> findConnectedLogs(Block firstLog) {47 List<Block> logs = new ArrayList<>();48 Set<Block> visited = new HashSet<>();49 Deque<Block> queue = new ArrayDeque<>();50 visited.add(firstLog);51 queue.add(firstLog);52 53 while (!queue.isEmpty() && logs.size() < MAX_LOGS) {54 Block current = queue.poll();55 for (int dx = -1; dx <= 1; dx++) {56 for (int dy = -1; dy <= 1; dy++) {57 for (int dz = -1; dz <= 1; dz++) {58 Block neighbor = current.getRelative(dx, dy, dz);59 if (Tag.LOGS.isTagged(neighbor.getType()) && visited.add(neighbor)) {60 logs.add(neighbor);61 queue.add(neighbor);62 }63 }64 }65 }66 }67 return logs;68 }69}- A safety limit: the search stops looking once it has found about this many logs. Without it, a huge connected forest or a log house could freeze the server in one tick.
- The tag for items: every kind of axe, so wooden and netherite axes both work.
- Sneaking players break a single log. Always give players a way out.
- If the axe broke during the loop, the hand is empty and we stop.
- Breaks one connected log with normal drops. The log the player broke is not in the list, because the event breaks it itself.
- Wears down the axe a bit for every extra log, like a real swing would.
- A set remembers every block we have already looked at, so we never process one twice. Sets are covered in Lists, sets and maps.
- A waiting line of blocks whose neighbors we still have to check. We add at the end and take from the front.
- Three nested loops visit all 26 neighbors around a block, including diagonals, because tree trunks and branches connect diagonally.
- Adds the block to the set and returns
trueonly if it was new. That makes it work as the "have we been here?" check.
The main class registers both listeners, as in the earlier chapters:
Where this file livesblocks-demosrcmainjavacomexampleblocksdemoBlocksDemoPlugin.java
The package com.example.blocksdemo is the folder path com/example/blocksdemo inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- blocks-demo/
- src/main/
- java/com/example/blocksdemo/Package com.example.blocksdemo
- AutoReplantListener.javaListener: reacts to events
- BlocksDemoPlugin.javayou are hereMain class: Paper starts here (named as main in plugin.yml)
- TreeFellerListener.javaListener: reacts to events
- resources/Files copied into the jar as they are
- plugin.ymlTells Paper the plugin's name, version and main class
- java/com/example/blocksdemo/Package com.example.blocksdemo
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
1package com.example.blocksdemo;2 3import org.bukkit.plugin.PluginManager;4import org.bukkit.plugin.java.JavaPlugin;5 6public final class BlocksDemoPlugin extends JavaPlugin {7 8 @Override9 public void onEnable() {10 PluginManager pluginManager = getServer().getPluginManager();11 pluginManager.registerEvents(new AutoReplantListener(), this);12 pluginManager.registerEvents(new TreeFellerListener(), this);13 }14}- Saved in a variable so the two
registerEventslines stay short.
- blocks-demo/
- src/
- main/
- java/
- com/example/blocksdemo/
- BlocksDemoPlugin.javaThe main class: registers both listeners
- AutoReplantListener.javaReplants ripe crops
- TreeFellerListener.javaFells connected logs with an axe
- com/example/blocksdemo/
- resources/
- plugin.ymlPlugin name, version and main class
- java/
- main/
- src/
Download the demo as a Gradle project, plant some wheat on your test world and break it when it is ripe. Then chop a tree with an axe.
Mistakes to avoid
- Changing
BlockDatabut not setting it back. It is a copy, so callblock.setBlockData(data). - Changing a
BlockStatebut not callingupdate(). Signs, spawners and other snapshots need it. - Casting without checking.
(Chest) block.getState()throws aClassCastExceptionon a barrel. Useinstanceof. - Assuming a
Blockobject stays the same. It is a live window onto the world, so its type can change at any moment. - Using
setType(AIR)when drops are wanted. That deletes the block. UsebreakNaturally. - Hard-coding the maximum age or a list of woods. Use
getMaximumAge()and tags. - Old tutorials with block "data values". Calls such as
block.setData((byte) 5)and numeric ids belong to Minecraft before 1.13. Today you useBlockData.
Frost walker
Write a listener that turns the water under a player's feet into ice, but only still source water, and only when the player moves to a new block. Flowing water should stay as it is.
Hint 1
You have seen the "only when the block changed" guard in Worlds, locations and movement.
Hint 2
Water is Material.WATER. Its data is Levelled, and a level of 0 means a source block. Do not forget to clone getTo() before you subtract.
Show the solution
The listener leaves early unless the player entered a new block, looks at the block below the feet, and turns it into ice when it is water at level 0.
Where this file livesblocks-exercisesrcmainjavacomexampleblocksexerciseFrostWalkerListener.java
The package com.example.blocksexercise is the folder path com/example/blocksexercise inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- blocks-exercise/
- src/main/
- java/com/example/blocksexercise/Package com.example.blocksexercise
- BlocksExercisePlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- FrostWalkerListener.javayou are hereListener: reacts to events
- resources/Files copied into the jar as they are
- plugin.ymlTells Paper the plugin's name, version and main class
- java/com/example/blocksexercise/Package com.example.blocksexercise
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
1package com.example.blocksexercise;2 3import org.bukkit.Material;4import org.bukkit.block.Block;5import org.bukkit.block.data.Levelled;6import org.bukkit.event.EventHandler;7import org.bukkit.event.Listener;8import org.bukkit.event.player.PlayerMoveEvent;9 10public final class FrostWalkerListener implements Listener {11 12 @EventHandler13 public void onMove(PlayerMoveEvent event) {14 if (!event.hasChangedBlock()) {15 return;16 }17 18 Block belowFeet = event.getTo().clone().subtract(0, 1, 0).getBlock();19 if (belowFeet.getType() != Material.WATER) {20 return;21 }22 if (belowFeet.getBlockData() instanceof Levelled water && water.getLevel() == 0) {23 belowFeet.setType(Material.ICE);24 }25 }26}- One block under the destination. The clone keeps the event's own location unchanged.
- Checks the data kind and then the level in one condition. Level 0 means a source block.
- Plain ice stays frozen until something warm melts it, so players leave a trail of ice behind them. A real frost walker enchantment uses
FROSTED_ICE, which melts on its own.
Wheat only
Change AutoReplantListener so it only replants wheat. Carrots and potatoes should break the normal way.
Hint
Add a check on block.getType() next to the existing tag check.
Show the solution
Replace the tag check with a direct comparison, because you now want exactly one crop:
1if (block.getType() != Material.WHEAT) {2 return;3}The rest of the handler stays the same. The Tag.CROPS import becomes unused and can go.
Recap
Blockis a spot in the world,Materialis its kind,BlockDataholds its settings andBlockStateis a snapshot including its contents.- Get blocks with
world.getBlockAt,location.getBlock(), an event, orgetRelative(BlockFace). setTypereplaces a block.breakNaturallybreaks it with drops. Both change the real world at once.- For settings, read
getBlockData(), change the copy and callsetBlockData. - For contents, read
getState(), change the snapshot and callupdate(). Chest inventories are live and need no update. - Use tags (
Tag.LOGS) instead of long lists of materials. - Use
instanceofwith a pattern variable instead of an unchecked cast.
Quick quiz
You want to make a placed oak stair face west. Which type do you use?
Facing is a setting of the block, and settings live inBlockData.Materialonly says the block is oak stairs, andBlockStateis for contents like items and text.You wrote
crop.setAge(7);on data fromblock.getBlockData(), but the crop in the world did not change. What is missing?getBlockData()returns a copy. Changes only reach the world when you hand the copy back withsetBlockData. Theupdate()call belongs toBlockState.What is the difference between
block.setType(Material.AIR)andblock.breakNaturally()?Replacing a block with air is silent and gives no drops.breakNaturallyacts like a player breaking it, including drops and Fortune from the tool you pass.When must you call
update()after changing something?ABlockStateis a copy, andupdate()writes it back.setTypeandsetBlockDataalready change the real block.Why does the demo use
Tag.LOGS.isTagged(type)and not a list likeOAK_LOG,BIRCH_LOG, and so on?Mojang keeps the tag up to date, so your plugin keeps working when new wood types arrive. Comparing materials with==works fine; it just needs a long list.
Next steps
- Entities and mobs: spawn and control the creatures that stand on your blocks.
- Items and ItemStacks: the items that blocks drop and that chests hold.
- Saving data on things: mark blocks and chests with your own data.
- Custom crafting recipes: let players craft new items from blocks.