Custom crafting recipes
Add shaped, shapeless, furnace and smithing recipes, including recipes for custom items.
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:
Making a recipe always follows the same three steps:
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.Describe the recipe
Create a recipe object with the key and the result item, then say what goes in.
Register it
Hand it to
Bukkit.addRecipe(recipe), usually inonEnable. 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".
Here is a whole plugin with one recipe: four diamonds around an apple make two golden apples. Read the notes:
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
- java/com/example/recipesfirst/Package com.example.recipesfirst
- 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.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}- A field, so both
onEnableandonDisablecan use the same key. - The recipe's unique name. It becomes
recipesfirst:diamond_apple. - The recipe object. The second argument is the result: two golden apples come out every time.
- The pattern, one text per row. Four diamonds sit in a plus shape around the middle. Spaces are empty slots.
- The middle row: diamond, apple, diamond.
- Says that every
Din the shape means a diamond. Use single quotes for a single letter (achar), double quotes for text. - Registers the recipe. From now on the crafting table knows it.
- When the plugin stops, takes the recipe out again, so a restart does not try to add it twice.
A few shape rules worth knowing:
- Every row in
shapemust 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
setIngredientis not an error: it quietly becomes an empty slot, so the recipe does not mean what you drew. AsetIngredientfor a letter that is not in the shape throws anIllegalArgumentException. - 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.
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
- java/com/example/recipesdemo/Package com.example.recipesdemo
- 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.
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}- The result is a custom item, a renamed bread, and the recipe produces two of them. The next section explains where it comes from.
- One bread, anywhere in the grid.
- 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:
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
- java/com/example/recipesdemo/Package com.example.recipesdemo
- 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.
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}- Starts from a normal diamond pickaxe.
- Changes the item's name and lore inside a small lambda.
- A helper that makes colored text with italics turned off. Item names are italic by default, which looks wrong for a "real" item.
- 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:
| Choice | Accepts | Use 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 |
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
- java/com/example/recipesdemo/Package com.example.recipesdemo
- 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.
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}- Three
Racross the top, then two sticks down the middle: the same shape as a pickaxe. - Each
Rcan 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. - Plain materials still work for simple slots.
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
- java/com/example/recipesdemo/Package com.example.recipesdemo
- 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.
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}- 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. - 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.
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
- java/com/example/recipesdemo/Package com.example.recipesdemo
- 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.
111private FurnaceRecipe furnaceLeather() {112 return new FurnaceRecipe(leatherKey, ItemStack.of(Material.LEATHER),113 Material.ROTTEN_FLESH, 0.3f, 200);114}- The result.
- The input. Smelting rotten flesh into leather.
- The experience the player gets when they take the result out. The
fmakes it afloat. - 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.
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
- java/com/example/recipesdemo/Package com.example.recipesdemo
- 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.
121private StonecuttingRecipe crushedGravel() {122 return new StonecuttingRecipe(gravelKey, ItemStack.of(Material.GRAVEL, 2), Material.COBBLESTONE);123}- 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:
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
- java/com/example/recipesdemo/Package com.example.recipesdemo
- 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.
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}- The result: a diamond sword with a new name and Sharpness V.
- The template slot. We reuse the netherite template, so players do not need a new item.
- The base slot: the item that is upgraded.
- The addition slot: what you pay with. A diamond sword plus an emerald plus the template gives the Emerald Blade.
- The last value is
copyDataComponents. Withtrue, 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.falsehands out exactly the Emerald Blade fromCustomItems.
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:
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
- java/com/example/recipesdemo/Package com.example.recipesdemo
- 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.
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}- Each line builds one recipe and registers it, so adding a new recipe later is one more line.
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
- java/com/example/recipesdemo/Package com.example.recipesdemo
- 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.
66private void add(Recipe recipe) {67 if (!Bukkit.addRecipe(recipe)) {68 plugin.getLogger().warning("Could not add a recipe: " + recipe.getResult().getType());69 }70}- Registering can fail (for example when the same key already exists).
addRecipeanswerstrueorfalse, so check it and log a warning rather than silently losing a recipe. - Every recipe can say what it produces, which makes the warning useful.
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
- java/com/example/recipesdemo/Package com.example.recipesdemo
- 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.
60public void unregisterAll() {61 for (NamespacedKey key : allKeys()) {62 Bukkit.removeRecipe(key);63 }64}- Removes a recipe by its key. It returns
truewhen 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.
| Call | What 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:
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
- java/com/example/recipesdemo/Package com.example.recipesdemo
- 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.
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}- Unlocks every recipe in the book and counts how many were new.
- Only speak up when something was really unlocked. Already-known recipes are skipped silently, so returning players see no message.
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, ornullwhen the grid matches nothing.getInventory(): theCraftingInventory, whosegetResult()andsetResult(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:
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
- java/com/example/recipesdemo/Package com.example.recipesdemo
- 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.
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}- 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. - Is it our recipe? Every other recipe, from vanilla or other plugins, is left alone.
- The preview item that is about to appear in the output slot.
- The person looking at this crafting table.
- Keeps the lore the item already has, and then the new line goes below it.
- Replaces the preview with the changed item. This is what the player picks up.
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:
1Bukkit.removeRecipe(NamespacedKey.minecraft("tnt"));- The key of the vanilla TNT recipe. The names are the vanilla ids, lowercase with underscores.
- Returns
falseif 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
- com/example/recipesdemo/
- resources/
- plugin.ymlPlugin name, version and main class
- java/
- main/
- src/
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
- java/com/example/recipesdemo/Package com.example.recipesdemo
- 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.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}- Creates the keys and recipes.
thisis the plugin, used for the key namespace. - Adds all seven recipes.
- The listener needs the book so it can unlock the recipes and recognize the pickaxe recipe.
- 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. Useruby_pickaxe. - Mismatched shape letters. A shape letter with no
setIngredientsilently becomes an empty slot, and asetIngredientfor 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
onDisableand check the result ofaddRecipe. - 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.
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
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
- java/com/example/recipesexercise/Package com.example.recipesexercise
- 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.
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}- Four cookies as the result.
- Three ingredients, and they work in any slots.
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:
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 withBukkit.addRecipe. Remove it withBukkit.removeRecipe(key). ShapedRecipeusesshapeandsetIngredient;ShapelessRecipeusesaddIngredient.- 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. PrepareItemCraftEventlets you change or block the preview result. Keep it short, because it fires often.
Quick quiz
Why does
new NamespacedKey(this, "Ruby_Pickaxe")throw an exception?A key is part of a resource name that must be lowercase. Useruby_pickaxe.You want a recipe that accepts only your custom "Healing Bread" and not normal bread. What do you use for that slot?
Item types only look at the kind of item, so every bread would match. An exact choice compares the whole item, including the name and lore.You added a furnace recipe, but it does nothing in the blast furnace. Why?
Furnace, blast furnace, smoker and campfire each have their own recipe class. Register one for every station that should accept the input.A player can craft your new recipe, but it is missing from the recipe book. What is missing?
Recipes work as soon as they are added, but they only show up in a player's book after being discovered.In
PrepareItemCraftEvent, what doesevent.getRecipe()return when the grid matches no recipe?The event fires for every grid change, including ones that match nothing, so always check fornullbefore using the recipe.
Next steps
- Items and ItemStacks: more ways to build the result items.
- Saving data on things: mark custom items so recipes and listeners recognize them reliably.
- Building GUI menus: a menu that lists your custom recipes.