Paper Plugin Guide
File mode0

Paper essentials

Custom crafting recipes

Add shaped, shapeless, furnace and smithing recipes, including recipes for custom items.

Beginner34 min read

Players love crafting something new. On this page you will add your own recipes to the crafting table, furnace, stonecutter and smithing table, use custom items as ingredients, unlock recipes in the recipe book, and change what players craft while they are crafting.

What is a recipe?

A recipe is a rule that says "put these things in, get that thing out". Minecraft has hundreds of them, and a plugin can add more. Each workstation has its own kind of recipe, and Paper has a Java class for each:

Crafting table ShapedRecipe ShapelessRecipe Furnace types FurnaceRecipe BlastingRecipe Stonecutter StonecuttingRecipe Smithing table SmithingTransform Recipe All of them need NamespacedKey then Bukkit.addRecipe
One recipe class for each workstation. Every recipe needs a NamespacedKey, and you register it with Bukkit.addRecipe.

Making a recipe always follows the same three steps:

  1. Make a key

    A NamespacedKey is the recipe's unique name, such as myplugin:diamond_apple. The game uses it to tell recipes apart, to remember who unlocked what, and to let you remove the recipe again.

  2. Describe the recipe

    Create a recipe object with the key and the result item, then say what goes in.

  3. Register it

    Hand it to Bukkit.addRecipe(recipe), usually in onEnable. Until then the game does not know it exists.

About keys

A key has two halves: a namespace (who owns it) and a key (what it is called). Vanilla recipes use the namespace minecraft. For your own recipes, write new NamespacedKey(this, "diamond_apple") inside your main class. The this is your plugin, and Paper uses the lowercase plugin name as the namespace, so two plugins can both have a recipe called diamond_apple without clashing.

Keys may only contain lowercase letters, digits, and the characters _ - . /. A capital letter or a space makes Paper throw an IllegalArgumentException right away. Old tutorials create recipes without a key (new ShapedRecipe(item)). That version is deprecated, because the game then loses track of the recipe after a restart. Always pass a key.

Your first recipe: shaped

A shaped recipe cares about where each ingredient sits in the crafting grid, like a pickaxe or a furnace. You draw the pattern with up to three rows of up to three letters, and then say which item each letter stands for. A space means "this slot stays empty".

A shaped crafting recipe A shaped recipe uses a 3 by 3 grid. The shape strings DDD, space S space and space S space place diamonds and sticks, the key maps D to DIAMOND and S to STICK, and the result is a diamond pickaxe. Crafting grid "DDD" D D D " S " S " S " S Result Diamond pickaxe DIAMOND_PICKAXE Key D DIAMOND S STICK recipe.shape("DDD", " S ", " S "); recipe.setIngredient('D', Material.DIAMOND); recipe.setIngredient('S', Material.STICK); In the shape, a space means the slot stays empty.
The shape rows are the grid, and each letter is an ingredient.

Here is a whole plugin with one recipe: four diamonds around an apple make two golden apples. Read the notes:

FirstRecipePlugin.javaCompiles on Paper 26.3Compile Lab
Where this file livesrecipes-firstsrcmainjavacomexamplerecipesfirstFirstRecipePlugin.java

The package com.example.recipesfirst is the folder path com/example/recipesfirst 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.

  • recipes-first/
    • src/main/
      • java/com/example/recipesfirst/Package com.example.recipesfirst
        • FirstRecipePlugin.javayou are hereMain class: Paper starts here (named as main in plugin.yml)
      • 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.recipesfirst;2 3import org.bukkit.Bukkit;4import org.bukkit.Material;5import org.bukkit.NamespacedKey;6import org.bukkit.inventory.ItemStack;7import org.bukkit.inventory.ShapedRecipe;8import org.bukkit.plugin.java.JavaPlugin;9 10public final class FirstRecipePlugin extends JavaPlugin {11 12    private NamespacedKey appleKey;13 14    @Override15    public void onEnable() {16        appleKey = new NamespacedKey(this, "diamond_apple");17        ShapedRecipe recipe = new ShapedRecipe(appleKey, ItemStack.of(Material.GOLDEN_APPLE, 2));18        recipe.shape(19            " D ",20            "DAD",21            " D ");22        recipe.setIngredient('D', Material.DIAMOND);23        recipe.setIngredient('A', Material.APPLE);24        Bukkit.addRecipe(recipe);25    }26 27    @Override28    public void onDisable() {29        Bukkit.removeRecipe(appleKey);30    }31}
  1. A field, so both onEnable and onDisable can use the same key.
  2. The recipe's unique name. It becomes recipesfirst:diamond_apple.
  3. The recipe object. The second argument is the result: two golden apples come out every time.
  4. The pattern, one text per row. Four diamonds sit in a plus shape around the middle. Spaces are empty slots.
  5. The middle row: diamond, apple, diamond.
  6. Says that every D in the shape means a diamond. Use single quotes for a single letter (a char), double quotes for text.
  7. Registers the recipe. From now on the crafting table knows it.
  8. When the plugin stops, takes the recipe out again, so a restart does not try to add it twice.
Golden Apple

A few shape rules worth knowing:

  • Every row in shape must have the same length. shape("DD", "DD") is a 2x2 recipe that works in the player's own crafting grid.
  • A letter in the shape that you forget to give a setIngredient is not an error: it quietly becomes an empty slot, so the recipe does not mean what you drew. A setIngredient for a letter that is not in the shape throws an IllegalArgumentException.
  • The game also accepts the mirror image of your pattern. Left-right mirrored pickaxes work too.

Shapeless recipes

A shapeless recipe only cares about which items are in the grid, not where, like dyes or a mushroom stew. You list the ingredients one after another. To need two of an item, add it twice or use the version with a count.

RecipeBook.javaCompiles on Paper 26.3Compile Lab
Where this file livesrecipes-demosrcmainjavacomexamplerecipesdemoRecipeBook.java

The package com.example.recipesdemo is the folder path com/example/recipesdemo 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.

  • recipes-demo/
    • src/main/
      • java/com/example/recipesdemo/Package com.example.recipesdemo
        • CustomItems.javaHelper class
        • RecipeBook.javayou are hereHelper class
        • RecipeListener.javaListener: reacts to events
        • RecipesDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
      • 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.

83private ShapelessRecipe healingBread() {84    ShapelessRecipe recipe = new ShapelessRecipe(healingBreadKey, CustomItems.healingBread(2));85    recipe.addIngredient(Material.BREAD);86    recipe.addIngredient(Material.GOLDEN_CARROT);87    recipe.addIngredient(Material.GLISTERING_MELON_SLICE);88    return recipe;89}
  1. The result is a custom item, a renamed bread, and the recipe produces two of them. The next section explains where it comes from.
  2. One bread, anywhere in the grid.
  3. Players can put the three ingredients in any slots, and it works.

Recipes for custom items

The result of a recipe is any ItemStack, so it can have a custom name, lore and enchantments. This helper class builds the three custom items used in this page. If any of this looks new, read Items and ItemStacks first:

CustomItems.javaCompiles on Paper 26.3Compile Lab
Where this file livesrecipes-demosrcmainjavacomexamplerecipesdemoCustomItems.java

The package com.example.recipesdemo is the folder path com/example/recipesdemo 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.

  • recipes-demo/
    • src/main/
      • java/com/example/recipesdemo/Package com.example.recipesdemo
        • CustomItems.javayou are hereHelper class
        • RecipeBook.javaHelper class
        • RecipeListener.javaListener: reacts to events
        • RecipesDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
      • 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.

16public static ItemStack rubyPickaxe() {17    ItemStack pickaxe = ItemStack.of(Material.DIAMOND_PICKAXE);18    pickaxe.editMeta(meta -> {19        meta.displayName(plain("Ruby Pickaxe", NamedTextColor.RED));20        meta.lore(List.of(plain("Mines blocks very fast.", NamedTextColor.GRAY)));21    });22    pickaxe.addUnsafeEnchantment(Enchantment.EFFICIENCY, 5);23    return pickaxe;24}
  1. Starts from a normal diamond pickaxe.
  2. Changes the item's name and lore inside a small lambda.
  3. A helper that makes colored text with italics turned off. Item names are italic by default, which looks wrong for a "real" item.
  4. Adds Efficiency V. "Unsafe" only means it ignores the usual rules about which enchantments fit which item. Efficiency V is already allowed on pickaxes.

RecipeChoice: more than one allowed item

Sometimes a slot should accept several items, or a very specific one. For that you give setIngredient a RecipeChoice instead of a plain material. There are two kinds you will use:

ChoiceAcceptsUse it for
RecipeChoice.itemType(ItemType.DIAMOND, ItemType.REDSTONE_BLOCK)Any one of the listed item types"Any of these will do", like any plank. The older name for the same idea is MaterialChoice, which you will see online.
RecipeChoice.exactChoice(itemStack)Only items that match this exact stack, including name, lore and enchantments (the stack size is ignored)A custom item as an ingredient
RecipeBook.javaCompiles on Paper 26.3Compile Lab
Where this file livesrecipes-demosrcmainjavacomexamplerecipesdemoRecipeBook.java

The package com.example.recipesdemo is the folder path com/example/recipesdemo 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.

  • recipes-demo/
    • src/main/
      • java/com/example/recipesdemo/Package com.example.recipesdemo
        • CustomItems.javaHelper class
        • RecipeBook.javayou are hereHelper class
        • RecipeListener.javaListener: reacts to events
        • RecipesDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
      • 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.

72private ShapedRecipe rubyPickaxe() {73    ShapedRecipe recipe = new ShapedRecipe(rubyPickaxeKey, CustomItems.rubyPickaxe());74    recipe.shape(75        "RRR",76        " S ",77        " S ");78    recipe.setIngredient('R', RecipeChoice.itemType(ItemType.REDSTONE_BLOCK, ItemType.DIAMOND));79    recipe.setIngredient('S', Material.STICK);80    return recipe;81}
  1. Three R across the top, then two sticks down the middle: the same shape as a pickaxe.
  2. Each R can be a redstone block or a diamond, in any mix. So a player can craft the Ruby Pickaxe with three diamonds, three redstone blocks, or two of one and one of the other.
  3. Plain materials still work for simple slots.
RecipeBook.javaCompiles on Paper 26.3Compile Lab
Where this file livesrecipes-demosrcmainjavacomexamplerecipesdemoRecipeBook.java

The package com.example.recipesdemo is the folder path com/example/recipesdemo 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.

  • recipes-demo/
    • src/main/
      • java/com/example/recipesdemo/Package com.example.recipesdemo
        • CustomItems.javaHelper class
        • RecipeBook.javayou are hereHelper class
        • RecipeListener.javaListener: reacts to events
        • RecipesDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
      • 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.

91private ShapedRecipe feastPlatter() {92    ShapedRecipe recipe = new ShapedRecipe(feastPlatterKey, ItemStack.of(Material.CAKE));93    recipe.shape(94        "BBB",95        "MMM");96    recipe.setIngredient('B', RecipeChoice.exactChoice(CustomItems.healingBread(1)));97    recipe.setIngredient('M', Material.COOKED_BEEF);98    return recipe;99}
  1. Only the Healing Bread from the previous recipe counts. A normal loaf of bread does not fit. If this used Material.BREAD, any bread would work and the custom item would mean nothing.
  2. Plain cooked beef is fine for the second row.

Furnace, blast furnace, stonecutter and smithing

Cooking recipes

A cooking recipe takes one item and turns it into another over time. FurnaceRecipe is the normal furnace. BlastingRecipe is the blast furnace, SmokingRecipe the smoker and CampfireRecipe the campfire. They all take the same five values: key, result, input, experience and cooking time in ticks.

RecipeBook.javaCompiles on Paper 26.3Compile Lab
Where this file livesrecipes-demosrcmainjavacomexamplerecipesdemoRecipeBook.java

The package com.example.recipesdemo is the folder path com/example/recipesdemo 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.

  • recipes-demo/
    • src/main/
      • java/com/example/recipesdemo/Package com.example.recipesdemo
        • CustomItems.javaHelper class
        • RecipeBook.javayou are hereHelper class
        • RecipeListener.javaListener: reacts to events
        • RecipesDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
      • 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.

111private FurnaceRecipe furnaceLeather() {112    return new FurnaceRecipe(leatherKey, ItemStack.of(Material.LEATHER),113        Material.ROTTEN_FLESH, 0.3f, 200);114}
  1. The result.
  2. The input. Smelting rotten flesh into leather.
  3. The experience the player gets when they take the result out. The f makes it a float.
  4. The cooking time in ticks. 200 ticks is 10 seconds, which is the normal furnace speed.

The blast furnace version in the same plugin uses 100 ticks, because blast furnaces are twice as fast. Add every cooking recipe you want separately; a recipe added for the furnace does not work in the blast furnace.

The stonecutter

A stonecutting recipe has no grid: one input, one result. Players pick the result from a menu.

RecipeBook.javaCompiles on Paper 26.3Compile Lab
Where this file livesrecipes-demosrcmainjavacomexamplerecipesdemoRecipeBook.java

The package com.example.recipesdemo is the folder path com/example/recipesdemo 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.

  • recipes-demo/
    • src/main/
      • java/com/example/recipesdemo/Package com.example.recipesdemo
        • CustomItems.javaHelper class
        • RecipeBook.javayou are hereHelper class
        • RecipeListener.javaListener: reacts to events
        • RecipesDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
      • 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.

121private StonecuttingRecipe crushedGravel() {122    return new StonecuttingRecipe(gravelKey, ItemStack.of(Material.GRAVEL, 2), Material.COBBLESTONE);123}
  1. One cobblestone becomes two gravel.

The smithing table

A smithing recipe has three inputs: a template, a base item that gets upgraded, and an addition. The netherite upgrade is the vanilla example. Each input is a RecipeChoice:

RecipeBook.javaCompiles on Paper 26.3Compile Lab
Where this file livesrecipes-demosrcmainjavacomexamplerecipesdemoRecipeBook.java

The package com.example.recipesdemo is the folder path com/example/recipesdemo 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.

  • recipes-demo/
    • src/main/
      • java/com/example/recipesdemo/Package com.example.recipesdemo
        • CustomItems.javaHelper class
        • RecipeBook.javayou are hereHelper class
        • RecipeListener.javaListener: reacts to events
        • RecipesDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
      • 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.

101private SmithingTransformRecipe emeraldBlade() {102    return new SmithingTransformRecipe(103        emeraldBladeKey,104        CustomItems.emeraldBlade(),105        RecipeChoice.itemType(ItemType.NETHERITE_UPGRADE_SMITHING_TEMPLATE),106        RecipeChoice.itemType(ItemType.DIAMOND_SWORD),107        RecipeChoice.itemType(ItemType.EMERALD),108        false);109}
  1. The result: a diamond sword with a new name and Sharpness V.
  2. The template slot. We reuse the netherite template, so players do not need a new item.
  3. The base slot: the item that is upgraded.
  4. The addition slot: what you pay with. A diamond sword plus an emerald plus the template gives the Emerald Blade.
  5. The last value is copyDataComponents. With true, which the shorter form uses, the base sword's own data (such as its enchantments) is copied onto the result and can replace what you built. false hands out exactly the Emerald Blade from CustomItems.

Putting them all in one place

Real plugins have many recipes. Keep them together in one class that creates the keys, builds the recipes, adds them and takes them away. Here is the part of RecipeBook that registers everything:

RecipeBook.javaCompiles on Paper 26.3Compile Lab
Where this file livesrecipes-demosrcmainjavacomexamplerecipesdemoRecipeBook.java

The package com.example.recipesdemo is the folder path com/example/recipesdemo 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.

  • recipes-demo/
    • src/main/
      • java/com/example/recipesdemo/Package com.example.recipesdemo
        • CustomItems.javaHelper class
        • RecipeBook.javayou are hereHelper class
        • RecipeListener.javaListener: reacts to events
        • RecipesDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
      • 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.

50public void registerAll() {51    add(rubyPickaxe());52    add(healingBread());53    add(feastPlatter());54    add(emeraldBlade());55    add(furnaceLeather());56    add(blastingLeather());57    add(crushedGravel());58}
  1. Each line builds one recipe and registers it, so adding a new recipe later is one more line.
RecipeBook.javaCompiles on Paper 26.3Compile Lab
Where this file livesrecipes-demosrcmainjavacomexamplerecipesdemoRecipeBook.java

The package com.example.recipesdemo is the folder path com/example/recipesdemo 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.

  • recipes-demo/
    • src/main/
      • java/com/example/recipesdemo/Package com.example.recipesdemo
        • CustomItems.javaHelper class
        • RecipeBook.javayou are hereHelper class
        • RecipeListener.javaListener: reacts to events
        • RecipesDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
      • 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.

66private void add(Recipe recipe) {67    if (!Bukkit.addRecipe(recipe)) {68        plugin.getLogger().warning("Could not add a recipe: " + recipe.getResult().getType());69    }70}
  1. Registering can fail (for example when the same key already exists). addRecipe answers true or false, so check it and log a warning rather than silently losing a recipe.
  2. Every recipe can say what it produces, which makes the warning useful.
RecipeBook.javaCompiles on Paper 26.3Compile Lab
Where this file livesrecipes-demosrcmainjavacomexamplerecipesdemoRecipeBook.java

The package com.example.recipesdemo is the folder path com/example/recipesdemo 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.

  • recipes-demo/
    • src/main/
      • java/com/example/recipesdemo/Package com.example.recipesdemo
        • CustomItems.javaHelper class
        • RecipeBook.javayou are hereHelper class
        • RecipeListener.javaListener: reacts to events
        • RecipesDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
      • 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.

60public void unregisterAll() {61    for (NamespacedKey key : allKeys()) {62        Bukkit.removeRecipe(key);63    }64}
  1. Removes a recipe by its key. It returns true when something was removed. Calling it for a key that does not exist is harmless.

The recipe book

Adding a recipe makes it craftable, but players do not see it in the recipe book (the green book next to the crafting grid) until they discover it. Recipes always work when a player places the items in the grid by hand, but the book is how players find out about them.

CallWhat it does
player.discoverRecipe(key)Unlocks one recipe for that player. Returns true if it was new.
player.discoverRecipes(keys)Unlocks many at once and returns how many were new.
player.hasDiscoveredRecipe(key)Asks whether they already have it.
player.undiscoverRecipe(key)Takes it away again.

The usual place for this is the join event, so everybody who ever logs in gets the recipes:

RecipeListener.javaCompiles on Paper 26.3Compile Lab
Where this file livesrecipes-demosrcmainjavacomexamplerecipesdemoRecipeListener.java

The package com.example.recipesdemo is the folder path com/example/recipesdemo 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.

  • recipes-demo/
    • src/main/
      • java/com/example/recipesdemo/Package com.example.recipesdemo
        • CustomItems.javaHelper class
        • RecipeBook.javaHelper class
        • RecipeListener.javayou are hereListener: reacts to events
        • RecipesDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
      • 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.

25@EventHandler26public void onJoin(PlayerJoinEvent event) {27    Player player = event.getPlayer();28    int unlocked = player.discoverRecipes(recipeBook.allKeys());29    if (unlocked > 0) {30        player.sendRichMessage("<green>You unlocked " + unlocked + " new crafting recipes.");31    }32}
  1. Unlocks every recipe in the book and counts how many were new.
  2. Only speak up when something was really unlocked. Already-known recipes are skipped silently, so returning players see no message.
You unlocked 7 new crafting recipes.

Changing results while crafting: PrepareItemCraftEvent

PrepareItemCraftEvent fires every time the items in a crafting grid change and Minecraft works out what the result would be. The player has not taken anything yet: the result is only a preview in the output slot. You can change that preview.

The event gives you:

  • getRecipe(): the matching recipe, or null when the grid matches nothing.
  • getInventory(): the CraftingInventory, whose getResult() and setResult(item) read and replace the preview. setResult(null) empties the output slot, so the player cannot craft.
  • getView().getPlayer(): who is crafting.

This listener adds a "Forged by" line with the player's name to every Ruby Pickaxe:

RecipeListener.javaCompiles on Paper 26.3Compile Lab
Where this file livesrecipes-demosrcmainjavacomexamplerecipesdemoRecipeListener.java

The package com.example.recipesdemo is the folder path com/example/recipesdemo 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.

  • recipes-demo/
    • src/main/
      • java/com/example/recipesdemo/Package com.example.recipesdemo
        • CustomItems.javaHelper class
        • RecipeBook.javaHelper class
        • RecipeListener.javayou are hereListener: reacts to events
        • RecipesDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
      • 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.

34@EventHandler35public void onPrepareCraft(PrepareItemCraftEvent event) {36    if (!(event.getRecipe() instanceof Keyed keyed)) {37        return;38    }39    if (!keyed.getKey().equals(recipeBook.rubyPickaxeKey())) {40        return;41    }42    CraftingInventory crafting = event.getInventory();43    ItemStack result = crafting.getResult();44    if (result == null) {45        return;46    }47    HumanEntity smith = event.getView().getPlayer();48    List<Component> lore = new ArrayList<>();49    if (result.lore() != null) {50        lore.addAll(result.lore());51    }52    lore.add(CustomItems.plain("Forged by " + smith.getName(), NamedTextColor.DARK_GRAY));53    result.lore(lore);54    crafting.setResult(result);55}
  1. The recipe can be null (nothing matches), and only recipes with a key can be told apart. This check handles both: if there is no keyed recipe, stop.
  2. Is it our recipe? Every other recipe, from vanilla or other plugins, is left alone.
  3. The preview item that is about to appear in the output slot.
  4. The person looking at this crafting table.
  5. Keeps the lore the item already has, and then the new line goes below it.
  6. Replaces the preview with the changed item. This is what the player picks up.
Ruby Pickaxe

Removing and replacing vanilla recipes

You can remove a vanilla recipe the same way you remove your own: with its key, in the minecraft namespace. After that, put your own recipe with a new key. Here is how to forbid crafting TNT:

Removing a vanilla recipe
1Bukkit.removeRecipe(NamespacedKey.minecraft("tnt"));
  1. The key of the vanilla TNT recipe. The names are the vanilla ids, lowercase with underscores.
  2. Returns false if there was no such recipe.

Be careful with two related methods. Bukkit.clearRecipes() removes every recipe in the game, vanilla included, and Bukkit.resetRecipes() brings back only the vanilla ones and drops everything plugins added. Both are almost never what you want.

The whole example plugin

  • recipes-demo/
    • src/
      • main/
        • java/
          • com/example/recipesdemo/
            • RecipesDemoPlugin.javaRegisters all recipes on enable and removes them on disable
            • RecipeBook.javaThe keys and every recipe: shaped, shapeless, smithing, furnace, blasting, stonecutter
            • CustomItems.javaThe Ruby Pickaxe, Healing Bread and Emerald Blade items
            • RecipeListener.javaUnlocks recipes on join and adds the "Forged by" line
        • resources/
          • plugin.ymlPlugin name, version and main class
RecipesDemoPlugin.javaCompiles on Paper 26.3Compile Lab
Where this file livesrecipes-demosrcmainjavacomexamplerecipesdemoRecipesDemoPlugin.java

The package com.example.recipesdemo is the folder path com/example/recipesdemo 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.

  • recipes-demo/
    • src/main/
      • java/com/example/recipesdemo/Package com.example.recipesdemo
        • CustomItems.javaHelper class
        • RecipeBook.javaHelper class
        • RecipeListener.javaListener: reacts to events
        • RecipesDemoPlugin.javayou are hereMain class: Paper starts here (named as main in plugin.yml)
      • 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.recipesdemo;2 3import org.bukkit.plugin.java.JavaPlugin;4 5public final class RecipesDemoPlugin extends JavaPlugin {6 7    private RecipeBook recipeBook;8 9    @Override10    public void onEnable() {11        recipeBook = new RecipeBook(this);12        recipeBook.registerAll();13        getServer().getPluginManager().registerEvents(new RecipeListener(recipeBook), this);14    }15 16    @Override17    public void onDisable() {18        recipeBook.unregisterAll();19    }20}
  1. Creates the keys and recipes. this is the plugin, used for the key namespace.
  2. Adds all seven recipes.
  3. The listener needs the book so it can unlock the recipes and recognize the pickaxe recipe.
  4. The recipes disappear when the plugin stops.

You can download the whole plugin, build it, and try everything on your test server (see Build, install and test): craft a Ruby Pickaxe, bake Healing Bread, craft the Feast Platter recipe (a cake) from three of them and three cooked beef, upgrade a diamond sword with an emerald, smelt rotten flesh, and cut cobblestone in the stonecutter.

Mistakes to avoid

  • Forgetting to register. A recipe you only create is not craftable. You need Bukkit.addRecipe.
  • An uppercase key. new NamespacedKey(this, "RubyPickaxe") throws. Use ruby_pickaxe.
  • Mismatched shape letters. A shape letter with no setIngredient silently becomes an empty slot, and a setIngredient for a letter that is not in the shape throws. Check both lists against each other.
  • Uneven shape rows. shape("DDD", "D") is invalid. Pad with spaces: "D ".
  • Using a plain material for a custom ingredient. Any bread then works. Use exactChoice.
  • Adding the same key twice, for example after a reload. Remove your recipes in onDisable and check the result of addRecipe.
  • Expecting a furnace recipe to work in the blast furnace. Register one recipe per workstation.
  • Heavy work in PrepareItemCraftEvent. It fires constantly. Check your recipe first and leave.
Try it

Berry cookies

Add a shapeless recipe that turns one sweet berry, one wheat and one sugar into four cookies. Remove the recipe when the plugin stops.

Hint 1

Copy the structure of the first recipe, but use ShapelessRecipe and addIngredient instead of shape.

Hint 2

The result is ItemStack.of(Material.COOKIE, 4). The material names are SWEET_BERRIES, WHEAT and SUGAR.

Show the solution
CookieRecipePlugin.javaCompiles on Paper 26.3Compile Lab
Where this file livesrecipes-exercisesrcmainjavacomexamplerecipesexerciseCookieRecipePlugin.java

The package com.example.recipesexercise is the folder path com/example/recipesexercise 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.

  • recipes-exercise/
    • src/main/
      • java/com/example/recipesexercise/Package com.example.recipesexercise
        • CookieRecipePlugin.javayou are hereMain class: Paper starts here (named as main in plugin.yml)
      • 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.

14@Override15public void onEnable() {16    cookieKey = new NamespacedKey(this, "berry_cookies");17    ShapelessRecipe recipe = new ShapelessRecipe(cookieKey, ItemStack.of(Material.COOKIE, 4));18    recipe.addIngredient(Material.SWEET_BERRIES);19    recipe.addIngredient(Material.WHEAT);20    recipe.addIngredient(Material.SUGAR);21    Bukkit.addRecipe(recipe);22}
  1. Four cookies as the result.
  2. Three ingredients, and they work in any slots.
Try it

No more TNT

Make it impossible to craft TNT on your server. Where in your plugin does the line go, and what is it?

Hint

Vanilla recipes have keys in the minecraft namespace. The TNT recipe is called tnt.

Show the solution

Put this line in onEnable:

In onEnable
1Bukkit.removeRecipe(NamespacedKey.minecraft("tnt"));

The recipe is gone for every player until the server restarts. Because vanilla recipes come back on restart, the line must run every time the plugin enables, which is exactly what onEnable does.

Recap

  • Every recipe has a NamespacedKey (lowercase, with your plugin as the namespace) and is registered with Bukkit.addRecipe. Remove it with Bukkit.removeRecipe(key).
  • ShapedRecipe uses shape and setIngredient; ShapelessRecipe uses addIngredient.
  • The result can be any custom ItemStack.
  • RecipeChoice.itemType(...) accepts any of several items; RecipeChoice.exactChoice(item) accepts only that custom item.
  • Furnace, blasting, smoking and campfire recipes need experience and a time in ticks; stonecutting has one input and one result; smithing has a template, a base and an addition.
  • Players see recipes in their book after discoverRecipe; do it on join.
  • PrepareItemCraftEvent lets you change or block the preview result. Keep it short, because it fires often.

Quick quiz

  1. Why does new NamespacedKey(this, "Ruby_Pickaxe") throw an exception?

  2. You want a recipe that accepts only your custom "Healing Bread" and not normal bread. What do you use for that slot?

  3. You added a furnace recipe, but it does nothing in the blast furnace. Why?

  4. A player can craft your new recipe, but it is missing from the recipe book. What is missing?

  5. In PrepareItemCraftEvent, what does event.getRecipe() return when the grid matches no recipe?

Next steps