diff --git a/feature-patches/1065-Improve-performance-of-mass-crafts.patch b/feature-patches/1065-Improve-performance-of-mass-crafts.patch
deleted file mode 100644
index c09f2cf750..0000000000
--- a/feature-patches/1065-Improve-performance-of-mass-crafts.patch
+++ /dev/null
@@ -1,92 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Jake Potrebic <jake.m.potrebic@gmail.com>
-Date: Sun, 13 Aug 2023 15:41:52 -0700
-Subject: [PATCH] Improve performance of mass crafts
-
-When the server crafts all available items in CraftingMenu or InventoryMenu the game
-checks either 4 or 9 times for each individual craft for a matching recipe for that container.
-This check can be expensive if 64 total crafts are being performed with the recipe matching logic
-being run 64 * 9 + 64 times. A breakdown of those times is below. This patch caches the last matching
-recipe so that it is checked first and only if it doesn't match does the rest of the matching logic run.
-
-Shift-click crafts are processed one at a time, so shift clicking on an item in the result of a iron block craft
-where all the 9 inputs are full stacks of iron will run 64 iron block crafts. For each of those crafts, the
-'remaining' blocks are calculated. This is due to recipes that have leftover items like buckets. This is done
-for each craft, and done once to get the full 9 leftover items which are usually air. Then 1 item is removed
-from each of the 9 inputs and each time that happens, logic is triggered to update the result itemstack. So
-for each craft, that logic is run 9 times (hence the 64 * 9). The + 64 is from the 64 checks for remaining items.
-
-After this patch, the full iteration over all recipes checking for a match should run once for a full craft to find the
-initial recipe match. Then that recipe will be checked first for all future recipe match checks.
-
-diff --git a/src/main/java/net/minecraft/world/inventory/CraftingContainer.java b/src/main/java/net/minecraft/world/inventory/CraftingContainer.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/inventory/CraftingContainer.java
-+++ b/src/main/java/net/minecraft/world/inventory/CraftingContainer.java
-@@ -0,0 +0,0 @@ public interface CraftingContainer extends Container, StackedContentsCompatible
-     List<ItemStack> getItems();
- 
-     // CraftBukkit start
--    default RecipeHolder<?> getCurrentRecipe() {
-+    default RecipeHolder<net.minecraft.world.item.crafting.CraftingRecipe> getCurrentRecipe() { // Paper - use correct generic
-         return null;
-     }
- 
--    default void setCurrentRecipe(RecipeHolder<?> recipe) {
-+    default void setCurrentRecipe(RecipeHolder<net.minecraft.world.item.crafting.CraftingRecipe> recipe) { // Paper - use correct generic
-     }
-     // CraftBukkit end
- 
-diff --git a/src/main/java/net/minecraft/world/inventory/CraftingMenu.java b/src/main/java/net/minecraft/world/inventory/CraftingMenu.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/inventory/CraftingMenu.java
-+++ b/src/main/java/net/minecraft/world/inventory/CraftingMenu.java
-@@ -0,0 +0,0 @@ public class CraftingMenu extends AbstractCraftingMenu {
-         CraftingInput craftinginput = craftingInventory.asCraftInput();
-         ServerPlayer entityplayer = (ServerPlayer) player;
-         ItemStack itemstack = ItemStack.EMPTY;
-+        if (recipe == null) recipe = craftingInventory.getCurrentRecipe(); // Paper - Perf: Improve mass crafting; check last recipe used first
-         Optional<RecipeHolder<CraftingRecipe>> optional = world.getServer().getRecipeManager().getRecipeFor(RecipeType.CRAFTING, craftinginput, world, recipe);
-         craftingInventory.setCurrentRecipe(optional.orElse(null)); // CraftBukkit
- 
-diff --git a/src/main/java/net/minecraft/world/inventory/ResultSlot.java b/src/main/java/net/minecraft/world/inventory/ResultSlot.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/inventory/ResultSlot.java
-+++ b/src/main/java/net/minecraft/world/inventory/ResultSlot.java
-@@ -0,0 +0,0 @@ public class ResultSlot extends Slot {
-     private NonNullList<ItemStack> getRemainingItems(CraftingInput input, Level world) {
-         return world instanceof ServerLevel serverLevel
-             ? serverLevel.recipeAccess()
--                .getRecipeFor(RecipeType.CRAFTING, input, serverLevel)
-+                .getRecipeFor(RecipeType.CRAFTING, input, serverLevel, this.craftSlots.getCurrentRecipe()) // Paper - Perf: Improve mass crafting; check last recipe used first
-                 .map(recipe -> recipe.value().getRemainingItems(input))
-                 .orElseGet(() -> copyAllInputItems(input))
-             : CraftingRecipe.defaultCraftingReminder(input);
-diff --git a/src/main/java/net/minecraft/world/inventory/TransientCraftingContainer.java b/src/main/java/net/minecraft/world/inventory/TransientCraftingContainer.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/inventory/TransientCraftingContainer.java
-+++ b/src/main/java/net/minecraft/world/inventory/TransientCraftingContainer.java
-@@ -0,0 +0,0 @@ public class TransientCraftingContainer implements CraftingContainer {
- 
-     // CraftBukkit start - add fields
-     public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
--    private RecipeHolder<?> currentRecipe;
-+    private RecipeHolder<net.minecraft.world.item.crafting.CraftingRecipe> currentRecipe; // Paper - use correct generic
-     public Container resultInventory;
-     private Player owner;
-     private int maxStack = MAX_STACK;
-@@ -0,0 +0,0 @@ public class TransientCraftingContainer implements CraftingContainer {
-     }
- 
-     @Override
--    public RecipeHolder<?> getCurrentRecipe() {
-+    public RecipeHolder<net.minecraft.world.item.crafting.CraftingRecipe> getCurrentRecipe() { // Paper - use correct generic
-         return this.currentRecipe;
-     }
- 
-     @Override
--    public void setCurrentRecipe(RecipeHolder<?> currentRecipe) {
-+    public void setCurrentRecipe(RecipeHolder<net.minecraft.world.item.crafting.CraftingRecipe> currentRecipe) { // Paper - use correct generic
-         this.currentRecipe = currentRecipe;
-     }
- 
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/CraftingContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/CraftingContainer.java.patch
index 2225d1d404..2aef7bff97 100644
--- a/paper-server/patches/sources/net/minecraft/world/inventory/CraftingContainer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/CraftingContainer.java.patch
@@ -5,11 +5,11 @@
      List<ItemStack> getItems();
  
 +    // CraftBukkit start
-+    default net.minecraft.world.item.crafting.RecipeHolder<?> getCurrentRecipe() {
++    default net.minecraft.world.item.crafting.RecipeHolder<net.minecraft.world.item.crafting.CraftingRecipe> getCurrentRecipe() {
 +        return null;
 +    }
 +
-+    default void setCurrentRecipe(net.minecraft.world.item.crafting.RecipeHolder<?> recipe) {
++    default void setCurrentRecipe(net.minecraft.world.item.crafting.RecipeHolder<net.minecraft.world.item.crafting.CraftingRecipe> recipe) {
 +    }
 +    // CraftBukkit end
 +
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/CraftingMenu.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/CraftingMenu.java.patch
index f490a6a0d3..d04a6f281e 100644
--- a/paper-server/patches/sources/net/minecraft/world/inventory/CraftingMenu.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/CraftingMenu.java.patch
@@ -18,9 +18,34 @@
          this.access = access;
          this.player = playerInventory.player;
          this.addResultSlot(this.player, 124, 35);
-@@ -56,6 +_,7 @@
+@@ -55,7 +_,32 @@
+         CraftingInput craftInput = craftSlots.asCraftInput();
          ServerPlayer serverPlayer = (ServerPlayer)player;
          ItemStack itemStack = ItemStack.EMPTY;
++        // Paper start - Perf: Improve mass crafting; check last recipe used first
++        /*
++        When the server crafts all available items in CraftingMenu or InventoryMenu the game
++        checks either 4 or 9 times for each individual craft for a matching recipe for that container.
++        This check can be expensive if 64 total crafts are being performed with the recipe matching logic
++        being run 64 * 9 + 64 times. A breakdown of those times is below. This caches the last matching
++        recipe so that it is checked first and only if it doesn't match does the rest of the matching logic run.
++
++        Shift-click crafts are processed one at a time, so shift clicking on an item in the result of a iron block craft
++        where all the 9 inputs are full stacks of iron will run 64 iron block crafts. For each of those crafts, the
++        'remaining' blocks are calculated. This is due to recipes that have leftover items like buckets. This is done
++        for each craft, and done once to get the full 9 leftover items which are usually air. Then 1 item is removed
++        from each of the 9 inputs and each time that happens, logic is triggered to update the result itemstack. So
++        for each craft, that logic is run 9 times (hence the 64 * 9). The + 64 is from the 64 checks for remaining items.
++
++        After this change, the full iteration over all recipes checking for a match should run once for a full craft to find the
++        initial recipe match. Then that recipe will be checked first for all future recipe match checks.
++
++        See also: ResultSlot class
++         */
++        if (recipe == null) {
++            recipe = craftSlots.getCurrentRecipe();
++        }
++        // Paper end - Perf: Improve mass crafting; check last recipe used first
          Optional<RecipeHolder<CraftingRecipe>> recipeFor = level.getServer().getRecipeManager().getRecipeFor(RecipeType.CRAFTING, craftInput, level, recipe);
 +        craftSlots.setCurrentRecipe(recipeFor.orElse(null)); // CraftBukkit
          if (recipeFor.isPresent()) {
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/ResultSlot.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/ResultSlot.java.patch
new file mode 100644
index 0000000000..c8c1e2c156
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/ResultSlot.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/inventory/ResultSlot.java
++++ b/net/minecraft/world/inventory/ResultSlot.java
+@@ -72,7 +_,7 @@
+     private NonNullList<ItemStack> getRemainingItems(CraftingInput input, Level level) {
+         return level instanceof ServerLevel serverLevel
+             ? serverLevel.recipeAccess()
+-                .getRecipeFor(RecipeType.CRAFTING, input, serverLevel)
++                .getRecipeFor(RecipeType.CRAFTING, input, serverLevel, this.craftSlots.getCurrentRecipe()) // Paper - Perf: Improve mass crafting; check last recipe used first
+                 .map(recipe -> recipe.value().getRemainingItems(input))
+                 .orElseGet(() -> copyAllInputItems(input))
+             : CraftingRecipe.defaultCraftingReminder(input);
diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/TransientCraftingContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/TransientCraftingContainer.java.patch
index 898f5850c4..85f7ca8636 100644
--- a/paper-server/patches/sources/net/minecraft/world/inventory/TransientCraftingContainer.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/inventory/TransientCraftingContainer.java.patch
@@ -6,7 +6,7 @@
  
 +    // CraftBukkit start - add fields
 +    public List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
-+    private net.minecraft.world.item.crafting.RecipeHolder<?> currentRecipe;
++    private net.minecraft.world.item.crafting.RecipeHolder<net.minecraft.world.item.crafting.CraftingRecipe> currentRecipe;
 +    public net.minecraft.world.Container resultInventory;
 +    private Player owner;
 +    private int maxStack = MAX_STACK;
@@ -51,12 +51,12 @@
 +    }
 +
 +    @Override
-+    public net.minecraft.world.item.crafting.RecipeHolder<?> getCurrentRecipe() {
++    public net.minecraft.world.item.crafting.RecipeHolder<net.minecraft.world.item.crafting.CraftingRecipe> getCurrentRecipe() {
 +        return this.currentRecipe;
 +    }
 +
 +    @Override
-+    public void setCurrentRecipe(net.minecraft.world.item.crafting.RecipeHolder<?> currentRecipe) {
++    public void setCurrentRecipe(net.minecraft.world.item.crafting.RecipeHolder<net.minecraft.world.item.crafting.CraftingRecipe> currentRecipe) {
 +        this.currentRecipe = currentRecipe;
 +    }
 +