--- a/net/minecraft/world/level/block/entity/BlockEntity.java
+++ b/net/minecraft/world/level/block/entity/BlockEntity.java
@@ -37,6 +_,11 @@
 import org.slf4j.Logger;
 
 public abstract class BlockEntity implements DebugValueSource {
+    static boolean ignoreBlockEntityUpdates; // Paper - Perf: Optimize Hoppers
+    // CraftBukkit start - data containers
+    private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry();
+    public final org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer persistentDataContainer;
+    // CraftBukkit end
     private static final Codec<BlockEntityType<?>> TYPE_CODEC = BuiltInRegistries.BLOCK_ENTITY_TYPE.byNameCodec();
     private static final Logger LOGGER = LogUtils.getLogger();
     private final BlockEntityType<?> type;
@@ -52,6 +_,7 @@
         this.worldPosition = pos.immutable();
         this.validateBlockState(blockState);
         this.blockState = blockState;
+        this.persistentDataContainer = new org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer(DATA_TYPE_REGISTRY); // Paper - always init
     }
 
     private void validateBlockState(BlockState state) {
@@ -68,6 +_,7 @@
         int intOr = tag.getIntOr("x", 0);
         int intOr1 = tag.getIntOr("y", 0);
         int intOr2 = tag.getIntOr("z", 0);
+        if (chunkPos != null) { // Paper - allow reading non-validated pos from tag - used to parse block entities on items
         int sectionPosCoord = SectionPos.blockToSectionCoord(intOr);
         int sectionPosCoord1 = SectionPos.blockToSectionCoord(intOr2);
         if (sectionPosCoord != chunkPos.x || sectionPosCoord1 != chunkPos.z) {
@@ -75,6 +_,7 @@
             intOr = chunkPos.getBlockX(SectionPos.sectionRelative(intOr));
             intOr2 = chunkPos.getBlockZ(SectionPos.sectionRelative(intOr2));
         }
+        } // Paper - allow reading non-validated pos from tag - used to parse block entities on items
 
         return new BlockPos(intOr, intOr1, intOr2);
     }
@@ -93,6 +_,12 @@
     }
 
     protected void loadAdditional(ValueInput input) {
+        // Paper start - read persistent data container
+        this.persistentDataContainer.clear(); // Paper - clear instead of init
+
+        input.read("PublicBukkitValues", CompoundTag.CODEC)
+            .ifPresent(this.persistentDataContainer::putAll);
+        // Paper end - read persistent data container
     }
 
     public final void loadWithComponents(ValueInput input) {
@@ -142,6 +_,11 @@
     public void saveWithoutMetadata(ValueOutput output) {
         this.saveAdditional(output);
         output.store("components", DataComponentMap.CODEC, this.components);
+        // CraftBukkit start - store container
+        if (!this.persistentDataContainer.isEmpty()) {
+            output.store("PublicBukkitValues", CompoundTag.CODEC, this.persistentDataContainer.toTagCompound());
+        }
+        // CraftBukkit end
     }
 
     public final CompoundTag saveCustomOnly(HolderLookup.Provider registries) {
@@ -157,9 +_,14 @@
 
     public void saveCustomOnly(ValueOutput output) {
         this.saveAdditional(output);
+        // Paper start - store PDC here as well
+        if (!this.persistentDataContainer.isEmpty()) {
+            output.store("PublicBukkitValues", CompoundTag.CODEC, this.persistentDataContainer.toTagCompound());
+        }
+        // Paper end
     }
 
-    private void saveId(ValueOutput output) {
+    public void saveId(ValueOutput output) {
         addEntityType(output, this.getType());
     }
 
@@ -206,6 +_,7 @@
 
     public void setChanged() {
         if (this.level != null) {
+            if (ignoreBlockEntityUpdates) return; // Paper - Perf: Optimize Hoppers
             setChanged(this.level, this.worldPosition, this.blockState);
         }
     }
@@ -289,6 +_,12 @@
     }
 
     public final void applyComponents(DataComponentMap components, DataComponentPatch patch) {
+        // CraftBukkit start
+        this.applyComponentsSet(components, patch);
+    }
+
+    public final Set<DataComponentType<?>> applyComponentsSet(DataComponentMap components, DataComponentPatch patch) {
+        // CraftBukkit end
         final Set<DataComponentType<?>> set = new HashSet<>();
         set.add(DataComponents.BLOCK_ENTITY_DATA);
         set.add(DataComponents.BLOCK_STATE);
@@ -309,6 +_,10 @@
         });
         DataComponentPatch dataComponentPatch = patch.forget(set::contains);
         this.components = dataComponentPatch.split().added();
+        // CraftBukkit start
+        set.remove(DataComponents.BLOCK_ENTITY_DATA); // Remove as never actually added by applyImplicitComponents
+        return set;
+        // CraftBukkit end
     }
 
     protected void collectImplicitComponents(DataComponentMap.Builder components) {
@@ -345,6 +_,27 @@
     @Override
     public void registerDebugValues(ServerLevel level, DebugValueSource.Registration registration) {
     }
+
+    // CraftBukkit start - add method
+    public org.bukkit.inventory.InventoryHolder getOwner() {
+        return getOwner(true);
+    }
+
+    public org.bukkit.inventory.InventoryHolder getOwner(boolean useSnapshot) {
+        if (this.level == null) return null;
+        org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(this.level, this.worldPosition);
+        org.bukkit.block.BlockState state = block.getState(useSnapshot); // Paper
+        return state instanceof final org.bukkit.inventory.InventoryHolder inventoryHolder ? inventoryHolder : null;
+    }
+    // CraftBukkit end
+
+    // Paper start - Sanitize sent data
+    public CompoundTag sanitizeSentNbt(CompoundTag tag) {
+        tag.remove("PublicBukkitValues");
+
+        return tag;
+    }
+    // Paper end - Sanitize sent data
 
     record BlockEntityPathElement(BlockEntity blockEntity) implements ProblemReporter.PathElement {
         @Override
