--- a/net/minecraft/world/entity/EntityType.java
+++ b/net/minecraft/world/entity/EntityType.java
@@ -176,6 +_,7 @@
 import net.minecraft.world.phys.Vec3;
 import net.minecraft.world.phys.shapes.Shapes;
 import net.minecraft.world.phys.shapes.VoxelShape;
+import org.bukkit.event.entity.CreatureSpawnEvent;
 import org.slf4j.Logger;
 
 public class EntityType<T extends Entity> implements FeatureElement, EntityTypeTest<Entity, T> {
@@ -215,7 +_,7 @@
             .fireImmune()
             .sized(6.0F, 0.5F)
             .clientTrackingRange(10)
-            .updateInterval(Integer.MAX_VALUE)
+            .updateInterval(10) // CraftBukkit - SPIGOT-3729: track area effect clouds
     );
     public static final EntityType<Armadillo> ARMADILLO = register(
         "armadillo", EntityType.Builder.of(Armadillo::new, MobCategory.CREATURE).sized(0.7F, 0.65F).eyeHeight(0.26F).clientTrackingRange(10)
@@ -1132,6 +_,22 @@
         boolean shouldOffsetY,
         boolean shouldOffsetYMore
     ) {
+        // CraftBukkit start
+        return this.spawn(level, spawnedFrom, player, pos, reason, shouldOffsetY, shouldOffsetYMore, reason == EntitySpawnReason.DISPENSER ? org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DISPENSE_EGG : org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG); // Paper - use correct spawn reason for dispenser spawn eggs
+    }
+
+    @Nullable
+    public T spawn(
+        ServerLevel level,
+        @Nullable ItemStack spawnedFrom,
+        @Nullable Player player,
+        BlockPos pos,
+        EntitySpawnReason reason,
+        boolean shouldOffsetY,
+        boolean shouldOffsetYMore,
+        org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason createSpawnReason
+    ) {
+        // CraftBukkit end
         Consumer<T> consumer;
         if (spawnedFrom != null) {
             consumer = createDefaultStackConfig(level, spawnedFrom, player);
@@ -1139,7 +_,7 @@
             consumer = entity -> {};
         }
 
-        return this.spawn(level, consumer, pos, reason, shouldOffsetY, shouldOffsetYMore);
+        return this.spawn(level, consumer, pos, reason, shouldOffsetY, shouldOffsetYMore, createSpawnReason); // CraftBukkit
     }
 
     public static <T extends Entity> Consumer<T> createDefaultStackConfig(Level level, ItemStack spawnedFrom, @Nullable Player player) {
@@ -1159,19 +_,54 @@
         Consumer<T> consumer, Level level, ItemStack spawnedFrom, @Nullable Player player
     ) {
         CustomData customData = spawnedFrom.getOrDefault(DataComponents.ENTITY_DATA, CustomData.EMPTY);
-        return !customData.isEmpty() ? consumer.andThen(entity -> updateCustomEntityTag(level, player, entity, customData)) : consumer;
+        // CraftBukkit start - SPIGOT-5665
+        return !customData.isEmpty() ? consumer.andThen(entity -> {
+            try {
+                updateCustomEntityTag(level, player, entity, customData);
+            } catch (Throwable t) {
+                EntityType.LOGGER.warn("Error loading spawn egg NBT", t);
+            }
+        }) : consumer;
+        // CraftBukkit end
     }
 
     @Nullable
     public T spawn(ServerLevel level, BlockPos pos, EntitySpawnReason reason) {
-        return this.spawn(level, null, pos, reason, false, false);
+        // CraftBukkit start
+        return this.spawn(level, pos, reason, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
+    }
+    @Nullable
+    public T spawn(ServerLevel level, BlockPos pos, EntitySpawnReason reason, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason creatureSpawnReason) {
+        return this.spawn(level, null, pos, reason, false, false, creatureSpawnReason);
+        // CraftBukkit End
     }
 
     @Nullable
     public T spawn(ServerLevel level, @Nullable Consumer<T> consumer, BlockPos pos, EntitySpawnReason reason, boolean shouldOffsetY, boolean shouldOffsetYMore) {
+        // CraftBukkit start
+        return this.spawn(level, consumer, pos, reason, shouldOffsetY, shouldOffsetYMore, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
+    }
+    @Nullable
+    public T spawn(ServerLevel level, @Nullable Consumer<T> consumer, BlockPos pos, EntitySpawnReason reason, boolean shouldOffsetY, boolean shouldOffsetYMore, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason creatureSpawnReason) {
+        // CraftBukkit end
+        // Paper start - PreCreatureSpawnEvent
+        com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent(
+            io.papermc.paper.util.MCUtil.toLocation(level, pos),
+            org.bukkit.craftbukkit.entity.CraftEntityType.minecraftToBukkit(this),
+            creatureSpawnReason
+        );
+        if (!event.callEvent()) {
+            return null;
+        }
+        // Paper end - PreCreatureSpawnEvent
         T entity = this.create(level, consumer, pos, reason, shouldOffsetY, shouldOffsetYMore);
         if (entity != null) {
-            level.addFreshEntityWithPassengers(entity);
+            // CraftBukkit start
+            level.addFreshEntityWithPassengers(entity, creatureSpawnReason);
+            if (entity.isRemoved()) {
+                return null; // Don't return an entity when CreatureSpawnEvent is canceled
+            }
+            // CraftBukkit end
             if (entity instanceof Mob mob) {
                 mob.playAmbientSound();
             }
@@ -1225,6 +_,15 @@
             EntityType<?> entityType = customData.parseEntityType(server.registryAccess(), Registries.ENTITY_TYPE);
             if (entity.getType() == entityType) {
                 if (level.isClientSide || !entity.getType().onlyOpCanSetNbt() || player != null && server.getPlayerList().isOp(player.getGameProfile())) {
+                    // Paper start - filter out protected tags
+                    if (player == null || !player.getBukkitEntity().hasPermission("minecraft.nbt.place")) {
+                        customData = customData.update((compound) -> {
+                            for (net.minecraft.commands.arguments.NbtPathArgument.NbtPath tag : level.paperConfig().entities.spawning.filteredEntityTagNbtPaths) {
+                                tag.remove(compound);
+                            }
+                        });
+                    }
+                    // Paper end - filter out protected tags
                     customData.loadInto(entity);
                 }
             }
@@ -1296,9 +_,19 @@
     }
 
     public static Optional<Entity> create(CompoundTag tag, Level level, EntitySpawnReason spawnReason) {
+        // Paper start - Don't fire sync event during generation
+        return create(tag, level, spawnReason, false);
+    }
+    public static Optional<Entity> create(CompoundTag tag, Level level, EntitySpawnReason spawnReason, boolean generation) {
+        // Paper end - Don't fire sync event during generation
         return Util.ifElse(
             by(tag).map(entityType -> entityType.create(level, spawnReason)),
-            entity -> entity.load(tag),
+            // Paper start - Don't fire sync event during generation
+            entity -> {
+                if (generation) entity.generation = true; // Paper - Don't fire sync event during generation
+                entity.load(tag);
+            },
+            // Paper end - Don't fire sync event during generation
             () -> LOGGER.warn("Skipping Entity with id {}", tag.getString("id"))
         );
     }
@@ -1325,7 +_,7 @@
     }
 
     public static Optional<EntityType<?>> by(CompoundTag tag) {
-        return BuiltInRegistries.ENTITY_TYPE.getOptional(ResourceLocation.parse(tag.getString("id")));
+        return BuiltInRegistries.ENTITY_TYPE.getOptional(ResourceLocation.tryParse(tag.getString("id"))); // Paper - Validate ResourceLocation
     }
 
     @Nullable
