--- a/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java
+++ b/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java
@@ -10,22 +10,61 @@
 import net.minecraft.world.level.block.DispenserBlock;
 import org.slf4j.Logger;
 
+// CraftBukkit start
+import org.bukkit.craftbukkit.block.CraftBlock;
+import org.bukkit.craftbukkit.inventory.CraftItemStack;
+import org.bukkit.event.block.BlockDispenseEvent;
+// CraftBukkit end
+
 public class ShulkerBoxDispenseBehavior extends OptionalDispenseItemBehavior {
+
     private static final Logger LOGGER = LogUtils.getLogger();
 
+    public ShulkerBoxDispenseBehavior() {}
+
     @Override
     protected ItemStack execute(BlockSource pointer, ItemStack stack) {
         this.setSuccess(false);
         Item item = stack.getItem();
+
         if (item instanceof BlockItem) {
-            Direction direction = pointer.state().getValue(DispenserBlock.FACING);
-            BlockPos blockPos = pointer.pos().relative(direction);
-            Direction direction2 = pointer.level().isEmptyBlock(blockPos.below()) ? direction : Direction.UP;
+            Direction enumdirection = (Direction) pointer.state().getValue(DispenserBlock.FACING);
+            BlockPos blockposition = pointer.pos().relative(enumdirection);
+            Direction enumdirection1 = pointer.level().isEmptyBlock(blockposition.below()) ? enumdirection : Direction.UP;
+
+            // CraftBukkit start
+            org.bukkit.block.Block bukkitBlock = CraftBlock.at(pointer.level(), pointer.pos());
+            CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - single item in event
+
+            BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ()));
+            if (!DispenserBlock.eventFired) {
+                pointer.level().getCraftServer().getPluginManager().callEvent(event);
+            }
+
+            if (event.isCancelled()) {
+                return stack;
+            }
+
+            if (!event.getItem().equals(craftItem)) {
+                // Chain to handler for new item
+                ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
+                DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem());
+                if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
+                    idispensebehavior.dispense(pointer, eventStack);
+                    return stack;
+                }
+            }
+            // CraftBukkit end
 
             try {
-                this.setSuccess(((BlockItem)item).place(new DirectionalPlaceContext(pointer.level(), blockPos, direction, stack, direction2)).consumesAction());
-            } catch (Exception var8) {
-                LOGGER.error("Error trying to place shulker box at {}", blockPos, var8);
+                // Paper start - track changed items in the dispense event
+                this.setSuccess(((BlockItem) item).place(new DirectionalPlaceContext(pointer.level(), blockposition, enumdirection, CraftItemStack.asNMSCopy(event.getItem()), enumdirection1)).consumesAction());
+                if (this.isSuccess()) {
+                    stack.shrink(1); // vanilla shrink is in the place function above, manually handle it here
+                }
+                // Paper end - track changed items in the dispense event
+            } catch (Exception exception) {
+                ShulkerBoxDispenseBehavior.LOGGER.error("Error trying to place shulker box at {}", blockposition, exception);
             }
         }
 
