Paper Plugin Guide
File mode0

Paper essentials

Blocks and block data

Read and change blocks, from simple stone to stairs, crops and chests.

Beginner39 min read

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, Material, BlockData and BlockState A Block is one spot in the world. It has a Location, a Material that says what kind of block it is, BlockData that holds its settings like facing, and a BlockState snapshot for blocks with extra data such as chests and signs. Block one spot in the world world.getBlockAt(10, 64, -3) Location getLocation() where it is: world, x, y, z Material getType() what kind it is: OAK_STAIRS BlockData getBlockData() its settings: facing, half, shape BlockState getState() a snapshot copy for chests, signs, furnaces A Block always shows the world right now. A BlockState is a copy taken at one moment: change it, then call update(). Use BlockData for settings and BlockState for extra data inside the block.
One spot in the world, seen through four different types.
  • 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 Block object 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: east and half: top under "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:

BlockSnippets.javaCompiles on Paper 26.3Compile Lab
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

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}
  1. The block at exact whole-number coordinates. These are block coordinates, as explained in Worlds, locations and movement.
  2. The block that a Location is inside. Works for player locations, entity locations and any location you built.
  3. The neighbor on one side. Here: the block under the player's feet.
  4. The neighbor two steps away in that direction.
  5. A neighbor by offsets in x, y and z, handy for diagonals. Here it is one block east and one block north.
block.getRelative(BlockFace.X) gives the neighbor on that side your block x, y, z UP y + 1 DOWN y - 1 WEST x - 1 EAST x + 1 NORTH z - 1 SOUTH z + 1
BlockFace names the six sides. NORTH is z - 1 and SOUTH is z + 1, just like on the coordinates diagram.

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

BlockSnippets.javaCompiles on Paper 26.3Compile Lab
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

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}
  1. The Material, such as STONE or OAK_LOG. Compare it with ==: block.getType() == Material.STONE.
  2. 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().
  3. 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:

BlockSnippets.javaCompiles on Paper 26.3Compile Lab
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

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}
  1. Turns the block into gold. Whatever it was before is gone, and nothing drops.
  2. The second argument is applyPhysics. false tells the game not to notify the neighbors about the change.
  3. 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:

BlockSnippets.javaCompiles on Paper 26.3Compile Lab
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

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}
  1. The item the player holds. It works as the tool for the break.
  2. Only asks what would drop and returns a list. The block stays where it is.
  3. Breaks the block and lets the drops fall as items on the ground.
You wantUse
Remove the block silentlyblock.setType(Material.AIR)
Break it with normal drops and effectsblock.breakNaturally(tool)
Know the drops without breakingblock.getDrops(tool)
Stop a player from breaking itevent.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:

BlockSnippets.javaCompiles on Paper 26.3Compile Lab
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

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}
  1. "If this block's data is an Ageable, call it crop." Blocks that are not plants skip the whole if.
  2. The age at which this plant is ripe. It is 7 for wheat, but only 3 for beetroots, so never hard-code the number.
  3. 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:

BlockSnippets.javaCompiles on Paper 26.3Compile Lab
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

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}
  1. Creates the default data for oak stairs. It returns the general BlockData type.
  2. A cast: "I know this is the Stairs kind of data." It is safe here because we just created data for stairs. For data you did not create yourself, use instanceof instead.
  3. The direction the stairs face. Only horizontal faces are valid.
  4. 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:

BlockSnippets.javaCompiles on Paper 26.3Compile Lab
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

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}
  1. Parses text like minecraft:oak_stairs[facing=east,half=top]. It throws an IllegalArgumentException if the text is not valid, so a typo shows up the first time the code runs.

Water in blocks: Waterlogged

BlockSnippets.javaCompiles on Paper 26.3Compile Lab
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

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}
  1. Only some blocks can hold water (stairs, slabs, fences, trapdoors). Others are not Waterlogged, so they skip this code.
  2. 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.

BlockSnippets.javaCompiles on Paper 26.3Compile Lab
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

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}
  1. Takes a snapshot of the block. If it is a sign, you get a Sign object for the copy. If the block is anything else, the code is skipped.
  2. Signs have two writable sides now, so you choose FRONT or BACK. The old sign.setLine methods are deprecated for this reason.
  3. Writes the first of the four lines. Lines are numbered from 0, and the text is a Component.
  4. 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:

BlockSnippets.javaCompiles on Paper 26.3Compile Lab
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

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}
  1. Furnaces, barrels, hoppers and shulker boxes work the same way through Container, the shared parent type.
  2. Puts three diamonds into the first free slot, just like a player would. Items and ItemStacks and Inventories go deeper.
0: diamond

Spawners are the same story as signs (change the snapshot, then update()):

BlockSnippets.javaCompiles on Paper 26.3Compile Lab
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

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}
  1. The state type for monster spawners.
  2. The spawner now makes zombies.
  3. 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.

BlockSnippets.javaCompiles on Paper 26.3Compile Lab
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

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}
  1. True for oak, birch, spruce and every other log, including stripped logs and any wood type added in the future.
  2. 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:

BlockEventSnippets.javaCompiles on Paper 26.3Compile Lab
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

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}
  1. The new block, already in the world when the event fires.
  2. The block the player clicked on to place this one, such as the ground.
  3. The item the player placed from.
  4. Stops the placement. Here, nobody can place TNT.
  5. The block that is about to break. It is still there while the event runs.
  6. The block breaks, but no items drop.
  7. 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.

AutoReplantListener.javaCompiles on Paper 26.3Compile Lab
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
    • 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

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}
  1. 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.
  2. Wheat, carrots, potatoes, beetroots and the other crops, without listing them.
  3. "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, so crop is safe to use there.
  4. In creative mode players should not get drops, so we let the normal break happen.
  5. Remembers which crop it was, because the block turns into air in a moment.
  6. We cancel the normal break and do the work ourselves, so we control what happens to the block afterwards.
  7. Breaks the ripe crop with normal drops (and Fortune from the tool).
  8. 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.

TreeFellerListener.javaCompiles on Paper 26.3Compile Lab
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
    • 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

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}
  1. 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.
  2. The tag for items: every kind of axe, so wooden and netherite axes both work.
  3. Sneaking players break a single log. Always give players a way out.
  4. If the axe broke during the loop, the hand is empty and we stop.
  5. Breaks one connected log with normal drops. The log the player broke is not in the list, because the event breaks it itself.
  6. Wears down the axe a bit for every extra log, like a real swing would.
  7. A set remembers every block we have already looked at, so we never process one twice. Sets are covered in Lists, sets and maps.
  8. A waiting line of blocks whose neighbors we still have to check. We add at the end and take from the front.
  9. Three nested loops visit all 26 neighbors around a block, including diagonals, because tree trunks and branches connect diagonally.
  10. Adds the block to the set and returns true only 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:

BlocksDemoPlugin.javaCompiles on Paper 26.3Compile Lab
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
    • 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

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}
  1. Saved in a variable so the two registerEvents lines 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
        • resources/
          • plugin.ymlPlugin name, version and main class

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 BlockData but not setting it back. It is a copy, so call block.setBlockData(data).
  • Changing a BlockState but not calling update(). Signs, spawners and other snapshots need it.
  • Casting without checking. (Chest) block.getState() throws a ClassCastException on a barrel. Use instanceof.
  • Assuming a Block object 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. Use breakNaturally.
  • 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 use BlockData.
Try it

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.

FrostWalkerListener.javaCompiles on Paper 26.3Compile Lab
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
    • 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

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}
  1. One block under the destination. The clone keeps the event's own location unchanged.
  2. Checks the data kind and then the level in one condition. Level 0 means a source block.
  3. 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.
Try it

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:

AutoReplantListener.java (changed lines)
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

  • Block is a spot in the world, Material is its kind, BlockData holds its settings and BlockState is a snapshot including its contents.
  • Get blocks with world.getBlockAt, location.getBlock(), an event, or getRelative(BlockFace).
  • setType replaces a block. breakNaturally breaks it with drops. Both change the real world at once.
  • For settings, read getBlockData(), change the copy and call setBlockData.
  • For contents, read getState(), change the snapshot and call update(). Chest inventories are live and need no update.
  • Use tags (Tag.LOGS) instead of long lists of materials.
  • Use instanceof with a pattern variable instead of an unchecked cast.

Quick quiz

  1. You want to make a placed oak stair face west. Which type do you use?

  2. You wrote crop.setAge(7); on data from block.getBlockData(), but the crop in the world did not change. What is missing?

  3. What is the difference between block.setType(Material.AIR) and block.breakNaturally()?

  4. When must you call update() after changing something?

  5. Why does the demo use Tag.LOGS.isTagged(type) and not a list like OAK_LOG, BIRCH_LOG, and so on?

Next steps