diff --git a/paper-api-generator/generated/io/papermc/paper/registry/keys/DamageTypeKeys.java b/paper-api-generator/generated/io/papermc/paper/registry/keys/DamageTypeKeys.java
new file mode 100644
index 0000000000..ed52d2393e
--- /dev/null
+++ b/paper-api-generator/generated/io/papermc/paper/registry/keys/DamageTypeKeys.java
@@ -0,0 +1,367 @@
+package io.papermc.paper.registry.keys;
+
+import static net.kyori.adventure.key.Key.key;
+
+import io.papermc.paper.generated.GeneratedFrom;
+import io.papermc.paper.registry.RegistryKey;
+import io.papermc.paper.registry.TypedKey;
+import net.kyori.adventure.key.Key;
+import org.bukkit.MinecraftExperimental;
+import org.bukkit.damage.DamageType;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Vanilla keys for {@link RegistryKey#DAMAGE_TYPE}.
+ *
+ * @apiNote The fields provided here are a direct representation of
+ * what is available from the vanilla game source. They may be
+ * changed (including removals) on any Minecraft version
+ * bump, so cross-version compatibility is not provided on the
+ * same level as it is on most of the other API.
+ */
+@SuppressWarnings({
+        "unused",
+        "SpellCheckingInspection"
+})
+@GeneratedFrom("1.20.6")
+@ApiStatus.Experimental
+public final class DamageTypeKeys {
+    /**
+     * {@code minecraft:arrow}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> ARROW = create(key("arrow"));
+
+    /**
+     * {@code minecraft:bad_respawn_point}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> BAD_RESPAWN_POINT = create(key("bad_respawn_point"));
+
+    /**
+     * {@code minecraft:cactus}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> CACTUS = create(key("cactus"));
+
+    /**
+     * {@code minecraft:cramming}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> CRAMMING = create(key("cramming"));
+
+    /**
+     * {@code minecraft:dragon_breath}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> DRAGON_BREATH = create(key("dragon_breath"));
+
+    /**
+     * {@code minecraft:drown}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> DROWN = create(key("drown"));
+
+    /**
+     * {@code minecraft:dry_out}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> DRY_OUT = create(key("dry_out"));
+
+    /**
+     * {@code minecraft:explosion}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> EXPLOSION = create(key("explosion"));
+
+    /**
+     * {@code minecraft:fall}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> FALL = create(key("fall"));
+
+    /**
+     * {@code minecraft:falling_anvil}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> FALLING_ANVIL = create(key("falling_anvil"));
+
+    /**
+     * {@code minecraft:falling_block}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> FALLING_BLOCK = create(key("falling_block"));
+
+    /**
+     * {@code minecraft:falling_stalactite}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> FALLING_STALACTITE = create(key("falling_stalactite"));
+
+    /**
+     * {@code minecraft:fireball}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> FIREBALL = create(key("fireball"));
+
+    /**
+     * {@code minecraft:fireworks}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> FIREWORKS = create(key("fireworks"));
+
+    /**
+     * {@code minecraft:fly_into_wall}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> FLY_INTO_WALL = create(key("fly_into_wall"));
+
+    /**
+     * {@code minecraft:freeze}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> FREEZE = create(key("freeze"));
+
+    /**
+     * {@code minecraft:generic}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> GENERIC = create(key("generic"));
+
+    /**
+     * {@code minecraft:generic_kill}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> GENERIC_KILL = create(key("generic_kill"));
+
+    /**
+     * {@code minecraft:hot_floor}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> HOT_FLOOR = create(key("hot_floor"));
+
+    /**
+     * {@code minecraft:in_fire}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> IN_FIRE = create(key("in_fire"));
+
+    /**
+     * {@code minecraft:in_wall}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> IN_WALL = create(key("in_wall"));
+
+    /**
+     * {@code minecraft:indirect_magic}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> INDIRECT_MAGIC = create(key("indirect_magic"));
+
+    /**
+     * {@code minecraft:lava}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> LAVA = create(key("lava"));
+
+    /**
+     * {@code minecraft:lightning_bolt}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> LIGHTNING_BOLT = create(key("lightning_bolt"));
+
+    /**
+     * {@code minecraft:magic}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> MAGIC = create(key("magic"));
+
+    /**
+     * {@code minecraft:mob_attack}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> MOB_ATTACK = create(key("mob_attack"));
+
+    /**
+     * {@code minecraft:mob_attack_no_aggro}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> MOB_ATTACK_NO_AGGRO = create(key("mob_attack_no_aggro"));
+
+    /**
+     * {@code minecraft:mob_projectile}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> MOB_PROJECTILE = create(key("mob_projectile"));
+
+    /**
+     * {@code minecraft:on_fire}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> ON_FIRE = create(key("on_fire"));
+
+    /**
+     * {@code minecraft:out_of_world}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> OUT_OF_WORLD = create(key("out_of_world"));
+
+    /**
+     * {@code minecraft:outside_border}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> OUTSIDE_BORDER = create(key("outside_border"));
+
+    /**
+     * {@code minecraft:player_attack}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> PLAYER_ATTACK = create(key("player_attack"));
+
+    /**
+     * {@code minecraft:player_explosion}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> PLAYER_EXPLOSION = create(key("player_explosion"));
+
+    /**
+     * {@code minecraft:sonic_boom}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> SONIC_BOOM = create(key("sonic_boom"));
+
+    /**
+     * {@code minecraft:spit}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> SPIT = create(key("spit"));
+
+    /**
+     * {@code minecraft:stalagmite}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> STALAGMITE = create(key("stalagmite"));
+
+    /**
+     * {@code minecraft:starve}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> STARVE = create(key("starve"));
+
+    /**
+     * {@code minecraft:sting}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> STING = create(key("sting"));
+
+    /**
+     * {@code minecraft:sweet_berry_bush}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> SWEET_BERRY_BUSH = create(key("sweet_berry_bush"));
+
+    /**
+     * {@code minecraft:thorns}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> THORNS = create(key("thorns"));
+
+    /**
+     * {@code minecraft:thrown}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> THROWN = create(key("thrown"));
+
+    /**
+     * {@code minecraft:trident}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> TRIDENT = create(key("trident"));
+
+    /**
+     * {@code minecraft:unattributed_fireball}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> UNATTRIBUTED_FIREBALL = create(key("unattributed_fireball"));
+
+    /**
+     * {@code minecraft:wind_charge}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    @ApiStatus.Experimental
+    @MinecraftExperimental(MinecraftExperimental.Requires.UPDATE_1_21)
+    public static final TypedKey<DamageType> WIND_CHARGE = create(key("wind_charge"));
+
+    /**
+     * {@code minecraft:wither}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> WITHER = create(key("wither"));
+
+    /**
+     * {@code minecraft:wither_skull}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<DamageType> WITHER_SKULL = create(key("wither_skull"));
+
+    private DamageTypeKeys() {
+    }
+
+    /**
+     * Creates a key for {@link DamageType} in a registry.
+     *
+     * @param key the value's key in the registry
+     * @return a new typed key
+     */
+    @ApiStatus.Experimental
+    public static @NotNull TypedKey<DamageType> create(final @NotNull Key key) {
+        return TypedKey.create(RegistryKey.DAMAGE_TYPE, key);
+    }
+}
diff --git a/paper-api-generator/generated/io/papermc/paper/registry/keys/EnchantmentKeys.java b/paper-api-generator/generated/io/papermc/paper/registry/keys/EnchantmentKeys.java
new file mode 100644
index 0000000000..3c64bc0790
--- /dev/null
+++ b/paper-api-generator/generated/io/papermc/paper/registry/keys/EnchantmentKeys.java
@@ -0,0 +1,336 @@
+package io.papermc.paper.registry.keys;
+
+import static net.kyori.adventure.key.Key.key;
+
+import io.papermc.paper.generated.GeneratedFrom;
+import io.papermc.paper.registry.RegistryKey;
+import io.papermc.paper.registry.TypedKey;
+import net.kyori.adventure.key.Key;
+import org.bukkit.MinecraftExperimental;
+import org.bukkit.enchantments.Enchantment;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Vanilla keys for {@link RegistryKey#ENCHANTMENT}.
+ *
+ * @apiNote The fields provided here are a direct representation of
+ * what is available from the vanilla game source. They may be
+ * changed (including removals) on any Minecraft version
+ * bump, so cross-version compatibility is not provided on the
+ * same level as it is on most of the other API.
+ */
+@SuppressWarnings({
+        "unused",
+        "SpellCheckingInspection"
+})
+@GeneratedFrom("1.20.6")
+@ApiStatus.Experimental
+public final class EnchantmentKeys {
+    /**
+     * {@code minecraft:aqua_affinity}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> AQUA_AFFINITY = create(key("aqua_affinity"));
+
+    /**
+     * {@code minecraft:bane_of_arthropods}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> BANE_OF_ARTHROPODS = create(key("bane_of_arthropods"));
+
+    /**
+     * {@code minecraft:binding_curse}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> BINDING_CURSE = create(key("binding_curse"));
+
+    /**
+     * {@code minecraft:blast_protection}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> BLAST_PROTECTION = create(key("blast_protection"));
+
+    /**
+     * {@code minecraft:breach}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    @ApiStatus.Experimental
+    @MinecraftExperimental(MinecraftExperimental.Requires.UPDATE_1_21)
+    public static final TypedKey<Enchantment> BREACH = create(key("breach"));
+
+    /**
+     * {@code minecraft:channeling}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> CHANNELING = create(key("channeling"));
+
+    /**
+     * {@code minecraft:density}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    @ApiStatus.Experimental
+    @MinecraftExperimental(MinecraftExperimental.Requires.UPDATE_1_21)
+    public static final TypedKey<Enchantment> DENSITY = create(key("density"));
+
+    /**
+     * {@code minecraft:depth_strider}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> DEPTH_STRIDER = create(key("depth_strider"));
+
+    /**
+     * {@code minecraft:efficiency}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> EFFICIENCY = create(key("efficiency"));
+
+    /**
+     * {@code minecraft:feather_falling}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> FEATHER_FALLING = create(key("feather_falling"));
+
+    /**
+     * {@code minecraft:fire_aspect}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> FIRE_ASPECT = create(key("fire_aspect"));
+
+    /**
+     * {@code minecraft:fire_protection}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> FIRE_PROTECTION = create(key("fire_protection"));
+
+    /**
+     * {@code minecraft:flame}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> FLAME = create(key("flame"));
+
+    /**
+     * {@code minecraft:fortune}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> FORTUNE = create(key("fortune"));
+
+    /**
+     * {@code minecraft:frost_walker}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> FROST_WALKER = create(key("frost_walker"));
+
+    /**
+     * {@code minecraft:impaling}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> IMPALING = create(key("impaling"));
+
+    /**
+     * {@code minecraft:infinity}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> INFINITY = create(key("infinity"));
+
+    /**
+     * {@code minecraft:knockback}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> KNOCKBACK = create(key("knockback"));
+
+    /**
+     * {@code minecraft:looting}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> LOOTING = create(key("looting"));
+
+    /**
+     * {@code minecraft:loyalty}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> LOYALTY = create(key("loyalty"));
+
+    /**
+     * {@code minecraft:luck_of_the_sea}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> LUCK_OF_THE_SEA = create(key("luck_of_the_sea"));
+
+    /**
+     * {@code minecraft:lure}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> LURE = create(key("lure"));
+
+    /**
+     * {@code minecraft:mending}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> MENDING = create(key("mending"));
+
+    /**
+     * {@code minecraft:multishot}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> MULTISHOT = create(key("multishot"));
+
+    /**
+     * {@code minecraft:piercing}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> PIERCING = create(key("piercing"));
+
+    /**
+     * {@code minecraft:power}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> POWER = create(key("power"));
+
+    /**
+     * {@code minecraft:projectile_protection}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> PROJECTILE_PROTECTION = create(key("projectile_protection"));
+
+    /**
+     * {@code minecraft:protection}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> PROTECTION = create(key("protection"));
+
+    /**
+     * {@code minecraft:punch}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> PUNCH = create(key("punch"));
+
+    /**
+     * {@code minecraft:quick_charge}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> QUICK_CHARGE = create(key("quick_charge"));
+
+    /**
+     * {@code minecraft:respiration}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> RESPIRATION = create(key("respiration"));
+
+    /**
+     * {@code minecraft:riptide}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> RIPTIDE = create(key("riptide"));
+
+    /**
+     * {@code minecraft:sharpness}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> SHARPNESS = create(key("sharpness"));
+
+    /**
+     * {@code minecraft:silk_touch}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> SILK_TOUCH = create(key("silk_touch"));
+
+    /**
+     * {@code minecraft:smite}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> SMITE = create(key("smite"));
+
+    /**
+     * {@code minecraft:soul_speed}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> SOUL_SPEED = create(key("soul_speed"));
+
+    /**
+     * {@code minecraft:sweeping_edge}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> SWEEPING_EDGE = create(key("sweeping_edge"));
+
+    /**
+     * {@code minecraft:swift_sneak}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> SWIFT_SNEAK = create(key("swift_sneak"));
+
+    /**
+     * {@code minecraft:thorns}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> THORNS = create(key("thorns"));
+
+    /**
+     * {@code minecraft:unbreaking}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> UNBREAKING = create(key("unbreaking"));
+
+    /**
+     * {@code minecraft:vanishing_curse}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Enchantment> VANISHING_CURSE = create(key("vanishing_curse"));
+
+    /**
+     * {@code minecraft:wind_burst}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    @ApiStatus.Experimental
+    @MinecraftExperimental(MinecraftExperimental.Requires.UPDATE_1_21)
+    public static final TypedKey<Enchantment> WIND_BURST = create(key("wind_burst"));
+
+    private EnchantmentKeys() {
+    }
+
+    private static @NotNull TypedKey<Enchantment> create(final @NotNull Key key) {
+        return TypedKey.create(RegistryKey.ENCHANTMENT, key);
+    }
+}
diff --git a/paper-api-generator/generated/io/papermc/paper/registry/keys/GameEventKeys.java b/paper-api-generator/generated/io/papermc/paper/registry/keys/GameEventKeys.java
index f220c63b8a..6d228f2f02 100644
--- a/paper-api-generator/generated/io/papermc/paper/registry/keys/GameEventKeys.java
+++ b/paper-api-generator/generated/io/papermc/paper/registry/keys/GameEventKeys.java
@@ -449,14 +449,7 @@ public final class GameEventKeys {
     private GameEventKeys() {
     }
 
-    /**
-     * Creates a key for {@link GameEvent} in a registry.
-     *
-     * @param key the value's key in the registry
-     * @return a new typed key
-     */
-    @ApiStatus.Experimental
-    public static @NotNull TypedKey<GameEvent> create(final @NotNull Key key) {
+    private static @NotNull TypedKey<GameEvent> create(final @NotNull Key key) {
         return TypedKey.create(RegistryKey.GAME_EVENT, key);
     }
 }
diff --git a/paper-api-generator/generated/io/papermc/paper/registry/keys/InstrumentKeys.java b/paper-api-generator/generated/io/papermc/paper/registry/keys/InstrumentKeys.java
new file mode 100644
index 0000000000..0cddaa1936
--- /dev/null
+++ b/paper-api-generator/generated/io/papermc/paper/registry/keys/InstrumentKeys.java
@@ -0,0 +1,91 @@
+package io.papermc.paper.registry.keys;
+
+import static net.kyori.adventure.key.Key.key;
+
+import io.papermc.paper.generated.GeneratedFrom;
+import io.papermc.paper.registry.RegistryKey;
+import io.papermc.paper.registry.TypedKey;
+import net.kyori.adventure.key.Key;
+import org.bukkit.MusicInstrument;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Vanilla keys for {@link RegistryKey#INSTRUMENT}.
+ *
+ * @apiNote The fields provided here are a direct representation of
+ * what is available from the vanilla game source. They may be
+ * changed (including removals) on any Minecraft version
+ * bump, so cross-version compatibility is not provided on the
+ * same level as it is on most of the other API.
+ */
+@SuppressWarnings({
+        "unused",
+        "SpellCheckingInspection"
+})
+@GeneratedFrom("1.20.6")
+@ApiStatus.Experimental
+public final class InstrumentKeys {
+    /**
+     * {@code minecraft:admire_goat_horn}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<MusicInstrument> ADMIRE_GOAT_HORN = create(key("admire_goat_horn"));
+
+    /**
+     * {@code minecraft:call_goat_horn}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<MusicInstrument> CALL_GOAT_HORN = create(key("call_goat_horn"));
+
+    /**
+     * {@code minecraft:dream_goat_horn}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<MusicInstrument> DREAM_GOAT_HORN = create(key("dream_goat_horn"));
+
+    /**
+     * {@code minecraft:feel_goat_horn}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<MusicInstrument> FEEL_GOAT_HORN = create(key("feel_goat_horn"));
+
+    /**
+     * {@code minecraft:ponder_goat_horn}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<MusicInstrument> PONDER_GOAT_HORN = create(key("ponder_goat_horn"));
+
+    /**
+     * {@code minecraft:seek_goat_horn}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<MusicInstrument> SEEK_GOAT_HORN = create(key("seek_goat_horn"));
+
+    /**
+     * {@code minecraft:sing_goat_horn}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<MusicInstrument> SING_GOAT_HORN = create(key("sing_goat_horn"));
+
+    /**
+     * {@code minecraft:yearn_goat_horn}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<MusicInstrument> YEARN_GOAT_HORN = create(key("yearn_goat_horn"));
+
+    private InstrumentKeys() {
+    }
+
+    private static @NotNull TypedKey<MusicInstrument> create(final @NotNull Key key) {
+        return TypedKey.create(RegistryKey.INSTRUMENT, key);
+    }
+}
diff --git a/paper-api-generator/generated/io/papermc/paper/registry/keys/MobEffectKeys.java b/paper-api-generator/generated/io/papermc/paper/registry/keys/MobEffectKeys.java
new file mode 100644
index 0000000000..83835bd7ff
--- /dev/null
+++ b/paper-api-generator/generated/io/papermc/paper/registry/keys/MobEffectKeys.java
@@ -0,0 +1,321 @@
+package io.papermc.paper.registry.keys;
+
+import static net.kyori.adventure.key.Key.key;
+
+import io.papermc.paper.generated.GeneratedFrom;
+import io.papermc.paper.registry.RegistryKey;
+import io.papermc.paper.registry.TypedKey;
+import net.kyori.adventure.key.Key;
+import org.bukkit.MinecraftExperimental;
+import org.bukkit.potion.PotionEffectType;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Vanilla keys for {@link RegistryKey#MOB_EFFECT}.
+ *
+ * @apiNote The fields provided here are a direct representation of
+ * what is available from the vanilla game source. They may be
+ * changed (including removals) on any Minecraft version
+ * bump, so cross-version compatibility is not provided on the
+ * same level as it is on most of the other API.
+ */
+@SuppressWarnings({
+        "unused",
+        "SpellCheckingInspection"
+})
+@GeneratedFrom("1.20.6")
+@ApiStatus.Experimental
+public final class MobEffectKeys {
+    /**
+     * {@code minecraft:absorption}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> ABSORPTION = create(key("absorption"));
+
+    /**
+     * {@code minecraft:bad_omen}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> BAD_OMEN = create(key("bad_omen"));
+
+    /**
+     * {@code minecraft:blindness}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> BLINDNESS = create(key("blindness"));
+
+    /**
+     * {@code minecraft:conduit_power}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> CONDUIT_POWER = create(key("conduit_power"));
+
+    /**
+     * {@code minecraft:darkness}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> DARKNESS = create(key("darkness"));
+
+    /**
+     * {@code minecraft:dolphins_grace}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> DOLPHINS_GRACE = create(key("dolphins_grace"));
+
+    /**
+     * {@code minecraft:fire_resistance}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> FIRE_RESISTANCE = create(key("fire_resistance"));
+
+    /**
+     * {@code minecraft:glowing}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> GLOWING = create(key("glowing"));
+
+    /**
+     * {@code minecraft:haste}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> HASTE = create(key("haste"));
+
+    /**
+     * {@code minecraft:health_boost}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> HEALTH_BOOST = create(key("health_boost"));
+
+    /**
+     * {@code minecraft:hero_of_the_village}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> HERO_OF_THE_VILLAGE = create(key("hero_of_the_village"));
+
+    /**
+     * {@code minecraft:hunger}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> HUNGER = create(key("hunger"));
+
+    /**
+     * {@code minecraft:infested}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    @ApiStatus.Experimental
+    @MinecraftExperimental(MinecraftExperimental.Requires.UPDATE_1_21)
+    public static final TypedKey<PotionEffectType> INFESTED = create(key("infested"));
+
+    /**
+     * {@code minecraft:instant_damage}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> INSTANT_DAMAGE = create(key("instant_damage"));
+
+    /**
+     * {@code minecraft:instant_health}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> INSTANT_HEALTH = create(key("instant_health"));
+
+    /**
+     * {@code minecraft:invisibility}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> INVISIBILITY = create(key("invisibility"));
+
+    /**
+     * {@code minecraft:jump_boost}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> JUMP_BOOST = create(key("jump_boost"));
+
+    /**
+     * {@code minecraft:levitation}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> LEVITATION = create(key("levitation"));
+
+    /**
+     * {@code minecraft:luck}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> LUCK = create(key("luck"));
+
+    /**
+     * {@code minecraft:mining_fatigue}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> MINING_FATIGUE = create(key("mining_fatigue"));
+
+    /**
+     * {@code minecraft:nausea}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> NAUSEA = create(key("nausea"));
+
+    /**
+     * {@code minecraft:night_vision}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> NIGHT_VISION = create(key("night_vision"));
+
+    /**
+     * {@code minecraft:oozing}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    @ApiStatus.Experimental
+    @MinecraftExperimental(MinecraftExperimental.Requires.UPDATE_1_21)
+    public static final TypedKey<PotionEffectType> OOZING = create(key("oozing"));
+
+    /**
+     * {@code minecraft:poison}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> POISON = create(key("poison"));
+
+    /**
+     * {@code minecraft:raid_omen}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    @ApiStatus.Experimental
+    @MinecraftExperimental(MinecraftExperimental.Requires.UPDATE_1_21)
+    public static final TypedKey<PotionEffectType> RAID_OMEN = create(key("raid_omen"));
+
+    /**
+     * {@code minecraft:regeneration}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> REGENERATION = create(key("regeneration"));
+
+    /**
+     * {@code minecraft:resistance}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> RESISTANCE = create(key("resistance"));
+
+    /**
+     * {@code minecraft:saturation}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> SATURATION = create(key("saturation"));
+
+    /**
+     * {@code minecraft:slow_falling}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> SLOW_FALLING = create(key("slow_falling"));
+
+    /**
+     * {@code minecraft:slowness}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> SLOWNESS = create(key("slowness"));
+
+    /**
+     * {@code minecraft:speed}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> SPEED = create(key("speed"));
+
+    /**
+     * {@code minecraft:strength}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> STRENGTH = create(key("strength"));
+
+    /**
+     * {@code minecraft:trial_omen}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    @ApiStatus.Experimental
+    @MinecraftExperimental(MinecraftExperimental.Requires.UPDATE_1_21)
+    public static final TypedKey<PotionEffectType> TRIAL_OMEN = create(key("trial_omen"));
+
+    /**
+     * {@code minecraft:unluck}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> UNLUCK = create(key("unluck"));
+
+    /**
+     * {@code minecraft:water_breathing}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> WATER_BREATHING = create(key("water_breathing"));
+
+    /**
+     * {@code minecraft:weakness}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> WEAKNESS = create(key("weakness"));
+
+    /**
+     * {@code minecraft:weaving}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    @ApiStatus.Experimental
+    @MinecraftExperimental(MinecraftExperimental.Requires.UPDATE_1_21)
+    public static final TypedKey<PotionEffectType> WEAVING = create(key("weaving"));
+
+    /**
+     * {@code minecraft:wind_charged}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    @ApiStatus.Experimental
+    @MinecraftExperimental(MinecraftExperimental.Requires.UPDATE_1_21)
+    public static final TypedKey<PotionEffectType> WIND_CHARGED = create(key("wind_charged"));
+
+    /**
+     * {@code minecraft:wither}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<PotionEffectType> WITHER = create(key("wither"));
+
+    private MobEffectKeys() {
+    }
+
+    private static @NotNull TypedKey<PotionEffectType> create(final @NotNull Key key) {
+        return TypedKey.create(RegistryKey.MOB_EFFECT, key);
+    }
+}
diff --git a/paper-api-generator/generated/io/papermc/paper/registry/keys/WolfVariantKeys.java b/paper-api-generator/generated/io/papermc/paper/registry/keys/WolfVariantKeys.java
new file mode 100644
index 0000000000..5ae854aba8
--- /dev/null
+++ b/paper-api-generator/generated/io/papermc/paper/registry/keys/WolfVariantKeys.java
@@ -0,0 +1,105 @@
+package io.papermc.paper.registry.keys;
+
+import static net.kyori.adventure.key.Key.key;
+
+import io.papermc.paper.generated.GeneratedFrom;
+import io.papermc.paper.registry.RegistryKey;
+import io.papermc.paper.registry.TypedKey;
+import net.kyori.adventure.key.Key;
+import org.bukkit.entity.Wolf;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Vanilla keys for {@link RegistryKey#WOLF_VARIANT}.
+ *
+ * @apiNote The fields provided here are a direct representation of
+ * what is available from the vanilla game source. They may be
+ * changed (including removals) on any Minecraft version
+ * bump, so cross-version compatibility is not provided on the
+ * same level as it is on most of the other API.
+ */
+@SuppressWarnings({
+        "unused",
+        "SpellCheckingInspection"
+})
+@GeneratedFrom("1.20.6")
+@ApiStatus.Experimental
+public final class WolfVariantKeys {
+    /**
+     * {@code minecraft:ashen}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Wolf.Variant> ASHEN = create(key("ashen"));
+
+    /**
+     * {@code minecraft:black}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Wolf.Variant> BLACK = create(key("black"));
+
+    /**
+     * {@code minecraft:chestnut}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Wolf.Variant> CHESTNUT = create(key("chestnut"));
+
+    /**
+     * {@code minecraft:pale}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Wolf.Variant> PALE = create(key("pale"));
+
+    /**
+     * {@code minecraft:rusty}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Wolf.Variant> RUSTY = create(key("rusty"));
+
+    /**
+     * {@code minecraft:snowy}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Wolf.Variant> SNOWY = create(key("snowy"));
+
+    /**
+     * {@code minecraft:spotted}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Wolf.Variant> SPOTTED = create(key("spotted"));
+
+    /**
+     * {@code minecraft:striped}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Wolf.Variant> STRIPED = create(key("striped"));
+
+    /**
+     * {@code minecraft:woods}
+     *
+     * @apiNote This field is version-dependant and may be removed in future Minecraft versions
+     */
+    public static final TypedKey<Wolf.Variant> WOODS = create(key("woods"));
+
+    private WolfVariantKeys() {
+    }
+
+    /**
+     * Creates a key for {@link Wolf.Variant} in a registry.
+     *
+     * @param key the value's key in the registry
+     * @return a new typed key
+     */
+    @ApiStatus.Experimental
+    public static @NotNull TypedKey<Wolf.Variant> create(final @NotNull Key key) {
+        return TypedKey.create(RegistryKey.WOLF_VARIANT, key);
+    }
+}
diff --git a/paper-api-generator/src/main/java/io/papermc/generator/Generators.java b/paper-api-generator/src/main/java/io/papermc/generator/Generators.java
index ac62e26e93..4afed26dd5 100644
--- a/paper-api-generator/src/main/java/io/papermc/generator/Generators.java
+++ b/paper-api-generator/src/main/java/io/papermc/generator/Generators.java
@@ -8,21 +8,31 @@ import net.minecraft.core.Registry;
 import net.minecraft.core.registries.Registries;
 import net.minecraft.resources.ResourceKey;
 import org.bukkit.GameEvent;
+import org.bukkit.MusicInstrument;
 import org.bukkit.block.Biome;
+import org.bukkit.damage.DamageType;
+import org.bukkit.enchantments.Enchantment;
+import org.bukkit.entity.Wolf;
 import org.bukkit.generator.structure.Structure;
 import org.bukkit.generator.structure.StructureType;
 import org.bukkit.inventory.meta.trim.TrimMaterial;
 import org.bukkit.inventory.meta.trim.TrimPattern;
+import org.bukkit.potion.PotionEffectType;
 
 public interface Generators {
 
     SourceGenerator[] API = {
-        simpleKey("GameEventKeys", GameEvent.class, Registries.GAME_EVENT, RegistryKey.GAME_EVENT, true),
+        simpleKey("GameEventKeys", GameEvent.class, Registries.GAME_EVENT, RegistryKey.GAME_EVENT, false),
         simpleKey("BiomeKeys", Biome.class, Registries.BIOME, RegistryKey.BIOME, true),
         simpleKey("TrimMaterialKeys", TrimMaterial.class, Registries.TRIM_MATERIAL, RegistryKey.TRIM_MATERIAL, true),
         simpleKey("TrimPatternKeys", TrimPattern.class, Registries.TRIM_PATTERN, RegistryKey.TRIM_PATTERN, true),
         simpleKey("StructureKeys", Structure.class, Registries.STRUCTURE, RegistryKey.STRUCTURE, true),
         simpleKey("StructureTypeKeys", StructureType.class, Registries.STRUCTURE_TYPE, RegistryKey.STRUCTURE_TYPE, false),
+        simpleKey("InstrumentKeys", MusicInstrument.class, Registries.INSTRUMENT, RegistryKey.INSTRUMENT, false),
+        simpleKey("EnchantmentKeys", Enchantment.class, Registries.ENCHANTMENT, RegistryKey.ENCHANTMENT, false),
+        simpleKey("MobEffectKeys", PotionEffectType.class, Registries.MOB_EFFECT, RegistryKey.MOB_EFFECT, false),
+        simpleKey("DamageTypeKeys", DamageType.class, Registries.DAMAGE_TYPE, RegistryKey.DAMAGE_TYPE, true),
+        simpleKey("WolfVariantKeys", Wolf.Variant.class, Registries.WOLF_VARIANT, RegistryKey.WOLF_VARIANT, true),
         new MobGoalGenerator("VanillaGoal", "com.destroystokyo.paper.entity.ai")
     };
 
diff --git a/paper-api-generator/src/main/java/io/papermc/generator/types/GeneratedKeyType.java b/paper-api-generator/src/main/java/io/papermc/generator/types/GeneratedKeyType.java
index 66c4a4b14f..8307fcad4c 100644
--- a/paper-api-generator/src/main/java/io/papermc/generator/types/GeneratedKeyType.java
+++ b/paper-api-generator/src/main/java/io/papermc/generator/types/GeneratedKeyType.java
@@ -25,11 +25,14 @@ import java.util.Set;
 import java.util.stream.Collectors;
 import net.kyori.adventure.key.Key;
 import net.minecraft.core.Holder;
+import net.minecraft.core.HolderLookup;
 import net.minecraft.core.Registry;
 import net.minecraft.core.RegistrySetBuilder;
 import net.minecraft.data.registries.UpdateOneTwentyOneRegistries;
 import net.minecraft.data.registries.VanillaRegistries;
 import net.minecraft.resources.ResourceKey;
+import net.minecraft.world.flag.FeatureElement;
+import net.minecraft.world.flag.FeatureFlags;
 import org.bukkit.MinecraftExperimental;
 import org.checkerframework.checker.nullness.qual.NonNull;
 import org.checkerframework.checker.nullness.qual.Nullable;
@@ -151,8 +154,23 @@ public class GeneratedKeyType<T, A> extends SimpleGenerator {
         return typeBuilder.addMethod(createMethod.build()).build();
     }
 
-    @SuppressWarnings("unchecked")
     private Set<ResourceKey<T>> collectExperimentalKeys(final Registry<T> registry) {
+        if (FeatureElement.FILTERED_REGISTRIES.contains(registry.key())) {
+            return this.collectExperimentalKeysBuiltIn(registry);
+        } else {
+            return this.collectExperimentalKeysDataDriven(registry);
+        }
+    }
+
+    private Set<ResourceKey<T>> collectExperimentalKeysBuiltIn(final Registry<T> registry) {
+        final HolderLookup.RegistryLookup<T> filteredLookup = registry.asLookup().filterElements(v -> {
+            return ((FeatureElement) v).requiredFeatures().contains(FeatureFlags.UPDATE_1_21);
+        });
+        return filteredLookup.listElementIds().collect(Collectors.toUnmodifiableSet());
+    }
+
+    @SuppressWarnings("unchecked")
+    private Set<ResourceKey<T>> collectExperimentalKeysDataDriven(final Registry<T> registry) {
         final RegistrySetBuilder.@Nullable RegistryBootstrap<T> experimentalBootstrap = (RegistrySetBuilder.RegistryBootstrap<T>) EXPERIMENTAL_REGISTRY_ENTRIES.get(this.registryKey);
         if (experimentalBootstrap == null) {
             return Collections.emptySet();
diff --git a/patches/api/Add-PaperRegistry.patch b/patches/api/Add-PaperRegistry.patch
deleted file mode 100644
index dbc8697ebd..0000000000
--- a/patches/api/Add-PaperRegistry.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: Wed, 2 Mar 2022 13:36:21 -0800
-Subject: [PATCH] Add PaperRegistry
-
-
-diff --git a/src/main/java/io/papermc/paper/registry/Reference.java b/src/main/java/io/papermc/paper/registry/Reference.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
---- /dev/null
-+++ b/src/main/java/io/papermc/paper/registry/Reference.java
-@@ -0,0 +0,0 @@
-+package io.papermc.paper.registry;
-+
-+import org.bukkit.Keyed;
-+import org.bukkit.NamespacedKey;
-+import org.bukkit.Registry;
-+import org.jetbrains.annotations.NotNull;
-+import org.jetbrains.annotations.Nullable;
-+
-+/**
-+ * Represents a reference to a server-backed registry value that may
-+ * change.
-+ *
-+ * @param <T> type of the value
-+ */
-+public interface Reference<T extends Keyed> extends Keyed {
-+
-+    /**
-+     * Gets the value from the registry with the key.
-+     *
-+     * @return the value
-+     * @throws java.util.NoSuchElementException if there is no value with this key
-+     */
-+    @NotNull T value();
-+
-+    /**
-+     * Gets the value from the registry with the key.
-+     *
-+     * @return the value or null if it doesn't exist
-+     */
-+    @Nullable T valueOrNull();
-+
-+    /**
-+     * Creates a reference to a registered value.
-+     *
-+     * @param registry the registry the value is located in
-+     * @param key the key to the value
-+     * @param <T> the type of the value
-+     * @return a reference
-+     */
-+    static <T extends Keyed> @NotNull Reference<T> create(@NotNull Registry<T> registry, @NotNull NamespacedKey key) {
-+        return new ReferenceImpl<>(registry, key);
-+    }
-+}
-diff --git a/src/main/java/io/papermc/paper/registry/ReferenceImpl.java b/src/main/java/io/papermc/paper/registry/ReferenceImpl.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
---- /dev/null
-+++ b/src/main/java/io/papermc/paper/registry/ReferenceImpl.java
-@@ -0,0 +0,0 @@
-+package io.papermc.paper.registry;
-+
-+import org.bukkit.Keyed;
-+import org.bukkit.NamespacedKey;
-+import org.bukkit.Registry;
-+import org.jetbrains.annotations.NotNull;
-+import org.jetbrains.annotations.Nullable;
-+
-+import java.util.NoSuchElementException;
-+
-+record ReferenceImpl<T extends Keyed>(@NotNull Registry<T> registry, @NotNull NamespacedKey key) implements Reference<T> {
-+
-+    @Override
-+    public @NotNull T value() {
-+        final T value = this.registry.get(this.key);
-+        if (value == null) {
-+            throw new NoSuchElementException("No such value with key " + this.key);
-+        }
-+        return value;
-+    }
-+
-+    @Override
-+    public @Nullable T valueOrNull() {
-+        return this.registry.get(this.key);
-+    }
-+
-+    @Override
-+    public @NotNull NamespacedKey getKey() {
-+        return this.key;
-+    }
-+}
diff --git a/patches/api/Add-RegistryAccess-for-managing-registries.patch b/patches/api/Add-RegistryAccess-for-managing-registries.patch
new file mode 100644
index 0000000000..3965dd7f1d
--- /dev/null
+++ b/patches/api/Add-RegistryAccess-for-managing-registries.patch
@@ -0,0 +1,394 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <jake.m.potrebic@gmail.com>
+Date: Wed, 2 Mar 2022 13:36:21 -0800
+Subject: [PATCH] Add RegistryAccess for managing registries
+
+
+diff --git a/src/main/java/io/papermc/paper/registry/Reference.java b/src/main/java/io/papermc/paper/registry/Reference.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/Reference.java
+@@ -0,0 +0,0 @@
++package io.papermc.paper.registry;
++
++import org.bukkit.Keyed;
++import org.bukkit.NamespacedKey;
++import org.bukkit.Registry;
++import org.jetbrains.annotations.NotNull;
++import org.jetbrains.annotations.Nullable;
++
++/**
++ * Represents a reference to a server-backed registry value that may
++ * change.
++ *
++ * @param <T> type of the value
++ */
++@Deprecated(forRemoval = true, since = "1.20.6")
++public interface Reference<T extends Keyed> extends Keyed {
++
++    /**
++     * Gets the value from the registry with the key.
++     *
++     * @return the value
++     * @throws java.util.NoSuchElementException if there is no value with this key
++     */
++    @Deprecated(forRemoval = true, since = "1.20.6")
++    @NotNull T value();
++
++    /**
++     * Gets the value from the registry with the key.
++     *
++     * @return the value or null if it doesn't exist
++     */
++    @Deprecated(forRemoval = true, since = "1.20.6")
++    @Nullable T valueOrNull();
++
++    /**
++     * Creates a reference to a registered value.
++     *
++     * @param registry the registry the value is located in
++     * @param key the key to the value
++     * @param <T> the type of the value
++     * @return a reference
++     */
++    @Deprecated(forRemoval = true, since = "1.20.6")
++    static <T extends Keyed> @NotNull Reference<T> create(@NotNull Registry<T> registry, @NotNull NamespacedKey key) {
++        return new ReferenceImpl<>(registry, key);
++    }
++}
+diff --git a/src/main/java/io/papermc/paper/registry/ReferenceImpl.java b/src/main/java/io/papermc/paper/registry/ReferenceImpl.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/ReferenceImpl.java
+@@ -0,0 +0,0 @@
++package io.papermc.paper.registry;
++
++import org.bukkit.Keyed;
++import org.bukkit.NamespacedKey;
++import org.bukkit.Registry;
++import org.jetbrains.annotations.NotNull;
++import org.jetbrains.annotations.Nullable;
++
++import java.util.NoSuchElementException;
++
++record ReferenceImpl<T extends Keyed>(@NotNull Registry<T> registry, @NotNull NamespacedKey key) implements Reference<T> {
++
++    @Override
++    public @NotNull T value() {
++        final T value = this.registry.get(this.key);
++        if (value == null) {
++            throw new NoSuchElementException("No such value with key " + this.key);
++        }
++        return value;
++    }
++
++    @Override
++    public @Nullable T valueOrNull() {
++        return this.registry.get(this.key);
++    }
++
++    @Override
++    public @NotNull NamespacedKey getKey() {
++        return this.key;
++    }
++}
+diff --git a/src/main/java/io/papermc/paper/registry/RegistryAccess.java b/src/main/java/io/papermc/paper/registry/RegistryAccess.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/RegistryAccess.java
+@@ -0,0 +0,0 @@
++package io.papermc.paper.registry;
++
++import org.bukkit.Keyed;
++import org.bukkit.Registry;
++import org.jetbrains.annotations.ApiStatus;
++import org.jetbrains.annotations.NotNull;
++import org.jetbrains.annotations.Nullable;
++
++/**
++ * Used for accessing different {@link Registry} instances
++ * by a {@link RegistryKey}. Get the main instance of {@link RegistryAccess}
++ * with {@link RegistryAccess#registryAccess()}.
++ */
++@ApiStatus.NonExtendable
++public interface RegistryAccess {
++
++    /**
++     * Get the {@link RegistryAccess} instance for the server.
++     *
++     * @return the RegistryAccess instance
++     */
++    static @NotNull RegistryAccess registryAccess() {
++        return RegistryAccessHolder.INSTANCE.orElseThrow(() -> new IllegalStateException("No RegistryAccess implementation found"));
++    }
++
++    /**
++     * Gets the registry based on the type.
++     *
++     * @param type the type
++     * @return the registry or null if none found
++     * @param <T> the type
++     * @deprecated use {@link #getRegistry(RegistryKey)} with keys from {@link RegistryKey}
++     */
++    @Deprecated(since = "1.20.6", forRemoval = true)
++    <T extends Keyed> @Nullable Registry<T> getRegistry(@NotNull Class<T> type);
++
++    /**
++     * Gets the registry with the specified key.
++     *
++     * @param registryKey the key
++     * @return the registry
++     * @param <T> the type
++     * @throws java.util.NoSuchElementException if no registry with the key is found
++     * @throws IllegalArgumentException if the registry is not available yet
++     */
++    // Future note: We should have no trouble removing this generic qualifier when
++    // registry types no longer have to be "keyed" as it shouldn't break ABI or API.
++    <T extends Keyed> @NotNull Registry<T> getRegistry(@NotNull RegistryKey<T> registryKey);
++}
+diff --git a/src/main/java/io/papermc/paper/registry/RegistryAccessHolder.java b/src/main/java/io/papermc/paper/registry/RegistryAccessHolder.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/RegistryAccessHolder.java
+@@ -0,0 +0,0 @@
++package io.papermc.paper.registry;
++
++import java.util.Optional;
++import java.util.ServiceLoader;
++
++final class RegistryAccessHolder {
++
++    static final Optional<RegistryAccess> INSTANCE = ServiceLoader.load(RegistryAccess.class).findFirst();
++
++    private RegistryAccessHolder() {
++    }
++}
+diff --git a/src/main/java/io/papermc/paper/registry/RegistryKeyImpl.java b/src/main/java/io/papermc/paper/registry/RegistryKeyImpl.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/io/papermc/paper/registry/RegistryKeyImpl.java
++++ b/src/main/java/io/papermc/paper/registry/RegistryKeyImpl.java
+@@ -0,0 +0,0 @@ record RegistryKeyImpl<T>(@NotNull Key key) implements RegistryKey<T> {
+ 
+     static final Set<RegistryKey<?>> REGISTRY_KEYS = Sets.newIdentityHashSet();
+ 
++    // override equals and hashCode to this can be used to simulate an "identity" hashmap
++    @Override
++    public boolean equals(final Object obj) {
++        return obj == this;
++    }
++
++    @Override
++    public int hashCode() {
++        return System.identityHashCode(this);
++    }
++
+     static <T> RegistryKey<T> create(@Subst("some_key") final String key) {
+         final RegistryKey<T> registryKey = createInternal(key);
+         REGISTRY_KEYS.add(registryKey);
+diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/org/bukkit/Bukkit.java
++++ b/src/main/java/org/bukkit/Bukkit.java
+@@ -0,0 +0,0 @@ public final class Bukkit {
+      * @param tClass of the registry to get
+      * @param <T> type of the registry
+      * @return the corresponding registry or null if not present
++     * @deprecated use {@link io.papermc.paper.registry.RegistryAccess#getRegistry(io.papermc.paper.registry.RegistryKey)}
++     * with keys from {@link io.papermc.paper.registry.RegistryKey}
+      */
+     @Nullable
++    @Deprecated(since = "1.20.6")
+     public static <T extends Keyed> Registry<T> getRegistry(@NotNull Class<T> tClass) {
+         return server.getRegistry(tClass);
+     }
+diff --git a/src/main/java/org/bukkit/Registry.java b/src/main/java/org/bukkit/Registry.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/org/bukkit/Registry.java
++++ b/src/main/java/org/bukkit/Registry.java
+@@ -0,0 +0,0 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
+      *
+      * @see Enchantment
+      */
+-    Registry<Enchantment> ENCHANTMENT = Objects.requireNonNull(Bukkit.getRegistry(Enchantment.class), "No registry present for Enchantment. This is a bug.");
++    Registry<Enchantment> ENCHANTMENT = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(io.papermc.paper.registry.RegistryKey.ENCHANTMENT); // Paper
+     /**
+      * Server entity types.
+      *
+@@ -0,0 +0,0 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
+      *
+      * @see MusicInstrument
+      */
+-    Registry<MusicInstrument> INSTRUMENT = Objects.requireNonNull(Bukkit.getRegistry(MusicInstrument.class), "No registry present for MusicInstrument. This is a bug.");
++    Registry<MusicInstrument> INSTRUMENT = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(io.papermc.paper.registry.RegistryKey.INSTRUMENT); // Paper
+     /**
+      * Default server loot tables.
+      *
+@@ -0,0 +0,0 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
+      *
+      * @see PotionEffectType
+      */
+-    Registry<PotionEffectType> EFFECT = Objects.requireNonNull(Bukkit.getRegistry(PotionEffectType.class), "No registry present for PotionEffectType. This is a bug.");
++    Registry<PotionEffectType> EFFECT = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(io.papermc.paper.registry.RegistryKey.MOB_EFFECT); // Paper
+     /**
+      * Server particles.
+      *
+@@ -0,0 +0,0 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
+      * Server structures.
+      *
+      * @see Structure
++     * @deprecated use {@link io.papermc.paper.registry.RegistryAccess#getRegistry(io.papermc.paper.registry.RegistryKey)} with {@link io.papermc.paper.registry.RegistryKey#STRUCTURE}
+      */
+-    Registry<Structure> STRUCTURE = Bukkit.getRegistry(Structure.class);
++    @Deprecated(since = "1.20.6") // Paper
++    Registry<Structure> STRUCTURE = Objects.requireNonNull(io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(Structure.class), "No registry present for Structure. This is a bug."); // Paper
+     /**
+      * Server structure types.
+      *
+      * @see StructureType
+      */
+-    Registry<StructureType> STRUCTURE_TYPE = Bukkit.getRegistry(StructureType.class);
++    Registry<StructureType> STRUCTURE_TYPE = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(io.papermc.paper.registry.RegistryKey.STRUCTURE_TYPE); // Paper
+     /**
+      * Sound keys.
+      *
+@@ -0,0 +0,0 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
+      * Trim materials.
+      *
+      * @see TrimMaterial
++     * @deprecated use {@link io.papermc.paper.registry.RegistryAccess#getRegistry(io.papermc.paper.registry.RegistryKey)} with {@link io.papermc.paper.registry.RegistryKey#TRIM_MATERIAL}
+      */
+-    Registry<TrimMaterial> TRIM_MATERIAL = Bukkit.getRegistry(TrimMaterial.class);
++    @Deprecated(since = "1.20.6") // Paper
++    Registry<TrimMaterial> TRIM_MATERIAL = Objects.requireNonNull(io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(TrimMaterial.class), "No registry present for TrimMaterial. This is a bug."); // Paper
+     /**
+      * Trim patterns.
+      *
+      * @see TrimPattern
++     * @deprecated use {@link io.papermc.paper.registry.RegistryAccess#getRegistry(io.papermc.paper.registry.RegistryKey)} with {@link io.papermc.paper.registry.RegistryKey#TRIM_PATTERN}
+      */
+-    Registry<TrimPattern> TRIM_PATTERN = Bukkit.getRegistry(TrimPattern.class);
++    @Deprecated(since = "1.20.6")
++    Registry<TrimPattern> TRIM_PATTERN = Objects.requireNonNull(io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(TrimPattern.class), "No registry present for TrimPattern. This is a bug."); // Paper
+     /**
+      * Damage types.
+      *
+      * @see DamageType
++     * @deprecated use {@link io.papermc.paper.registry.RegistryAccess#getRegistry(io.papermc.paper.registry.RegistryKey)} with {@link io.papermc.paper.registry.RegistryKey#DAMAGE_TYPE}
+      */
+-    @ApiStatus.Experimental
+-    Registry<DamageType> DAMAGE_TYPE = Objects.requireNonNull(Bukkit.getRegistry(DamageType.class), "No registry present for DamageType. This is a bug.");
++    @Deprecated(since = "1.20.6")
++    Registry<DamageType> DAMAGE_TYPE = Objects.requireNonNull(io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(DamageType.class), "No registry present for DamageType. This is a bug."); // Paper
+     /**
+      * Villager profession.
+      *
+@@ -0,0 +0,0 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
+      * Wolf variants.
+      *
+      * @see Wolf.Variant
++     * @deprecated use {@link io.papermc.paper.registry.RegistryAccess#getRegistry(io.papermc.paper.registry.RegistryKey)} with {@link io.papermc.paper.registry.RegistryKey#WOLF_VARIANT}
+      */
+-    Registry<Wolf.Variant> WOLF_VARIANT = Objects.requireNonNull(Bukkit.getRegistry(Wolf.Variant.class), "No registry present for Wolf Variant. This is a bug.");
++    @Deprecated(since = "1.20.6")
++    Registry<Wolf.Variant> WOLF_VARIANT = Objects.requireNonNull(io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(Wolf.Variant.class), "No registry present for Wolf$Variant. This is a bug."); // Paper
+     /**
+      * Map cursor types.
+      *
+@@ -0,0 +0,0 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
+      *
+      * @see GameEvent
+      */
+-    Registry<GameEvent> GAME_EVENT = Objects.requireNonNull(Bukkit.getRegistry(GameEvent.class), "No registry present for GameEvent. This is a bug.");
++    Registry<GameEvent> GAME_EVENT = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(io.papermc.paper.registry.RegistryKey.GAME_EVENT); // Paper
+     /**
+      * Get the object by its key.
+      *
+diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/org/bukkit/Server.java
++++ b/src/main/java/org/bukkit/Server.java
+@@ -0,0 +0,0 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
+      * @param tClass of the registry to get
+      * @param <T> type of the registry
+      * @return the corresponding registry or null if not present
++     * @deprecated use {@link io.papermc.paper.registry.RegistryAccess#getRegistry(io.papermc.paper.registry.RegistryKey)}
++     * with keys from {@link io.papermc.paper.registry.RegistryKey}
+      */
+     @Nullable
++    @Deprecated(since = "1.20.6") // Paper
+     <T extends Keyed> Registry<T> getRegistry(@NotNull Class<T> tClass);
+ 
+     /**
+diff --git a/src/test/java/io/papermc/paper/registry/TestRegistryAccess.java b/src/test/java/io/papermc/paper/registry/TestRegistryAccess.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/test/java/io/papermc/paper/registry/TestRegistryAccess.java
+@@ -0,0 +0,0 @@
++package io.papermc.paper.registry;
++
++import org.bukkit.Keyed;
++import org.bukkit.Registry;
++import org.jetbrains.annotations.NotNull;
++import org.jetbrains.annotations.Nullable;
++
++public class TestRegistryAccess implements RegistryAccess {
++
++    @Override
++    @Deprecated(since = "1.20.6", forRemoval = true)
++    public @Nullable <T extends Keyed> Registry<T> getRegistry(final @NotNull Class<T> type) {
++        throw new UnsupportedOperationException("Not supported");
++    }
++
++    @Override
++    public @NotNull <T extends Keyed> Registry<T> getRegistry(final @NotNull RegistryKey<T> registryKey) {
++        throw new UnsupportedOperationException("Not supported");
++    }
++}
+diff --git a/src/test/java/org/bukkit/support/TestServer.java b/src/test/java/org/bukkit/support/TestServer.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/test/java/org/bukkit/support/TestServer.java
++++ b/src/test/java/org/bukkit/support/TestServer.java
+@@ -0,0 +0,0 @@ public final class TestServer {
+ 
+         when(instance.getBukkitVersion()).thenReturn("BukkitVersion_" + TestServer.class.getPackage().getImplementationVersion());
+ 
+-        Map<Class<? extends Keyed>, Registry<?>> registers = new HashMap<>();
+-        when(instance.getRegistry(any())).then(invocationOnMock -> registers.computeIfAbsent(invocationOnMock.getArgument(0), aClass -> new Registry<Keyed>() {
+-            private final Map<NamespacedKey, Keyed> cache = new HashMap<>();
+-
+-            @Override
+-            public Keyed get(NamespacedKey key) {
+-                return cache.computeIfAbsent(key, key2 -> mock(aClass, withSettings().stubOnly()));
+-            }
+-
+-            @NotNull
+-            @Override
+-            public Stream<Keyed> stream() {
+-                throw new UnsupportedOperationException("Not supported");
+-            }
+-
+-            @Override
+-            public Iterator<Keyed> iterator() {
+-                throw new UnsupportedOperationException("Not supported");
+-            }
+-        }));
++        // Paper start - RegistryAccess
++        when(instance.getRegistry(any())).then(invocationOnMock -> {
++            return io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(((Class<Keyed>)invocationOnMock.getArgument(0)));
++        });
++        // Paper end - RegistryAccess
+ 
+         UnsafeValues unsafeValues = mock(withSettings().stubOnly());
+         when(instance.getUnsafe()).thenReturn(unsafeValues);
+diff --git a/src/test/resources/META-INF/services/io.papermc.paper.registry.RegistryAccess b/src/test/resources/META-INF/services/io.papermc.paper.registry.RegistryAccess
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/test/resources/META-INF/services/io.papermc.paper.registry.RegistryAccess
+@@ -0,0 +1 @@
++io.papermc.paper.registry.TestRegistryAccess
diff --git a/patches/api/Add-StructuresLocateEvent.patch b/patches/api/Add-StructuresLocateEvent.patch
index e67d4f1dbb..b9c4a511c1 100644
--- a/patches/api/Add-StructuresLocateEvent.patch
+++ b/patches/api/Add-StructuresLocateEvent.patch
@@ -519,18 +519,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 @@ -0,0 +0,0 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
       * @see GameEvent
       */
-     Registry<GameEvent> GAME_EVENT = Objects.requireNonNull(Bukkit.getRegistry(GameEvent.class), "No registry present for GameEvent. This is a bug.");
-+
+     Registry<GameEvent> GAME_EVENT = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(io.papermc.paper.registry.RegistryKey.GAME_EVENT); // Paper
 +    // Paper start
 +    /**
 +     * Configured structures.
 +     * @see io.papermc.paper.world.structure.ConfiguredStructure
-+     * @deprecated use {@link #STRUCTURE}
++     * @deprecated use {@link io.papermc.paper.registry.RegistryAccess#getRegistry(io.papermc.paper.registry.RegistryKey)} with {@link io.papermc.paper.registry.RegistryKey#STRUCTURE}
 +     */
 +    @Deprecated(forRemoval = true)
-+    Registry<io.papermc.paper.world.structure.ConfiguredStructure> CONFIGURED_STRUCTURE = Bukkit.getRegistry(io.papermc.paper.world.structure.ConfiguredStructure.class);
++    Registry<io.papermc.paper.world.structure.ConfiguredStructure> CONFIGURED_STRUCTURE = Objects.requireNonNull(io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(io.papermc.paper.world.structure.ConfiguredStructure.class), "No registry present for ConfiguredStructure. This is a bug.");
 +    // Paper end
-+
      /**
       * Get the object by its key.
       *
diff --git a/patches/api/Code-Generation.patch b/patches/api/Code-Generation.patch
index 402fca8f0b..787a87fe49 100644
--- a/patches/api/Code-Generation.patch
+++ b/patches/api/Code-Generation.patch
@@ -92,13 +92,30 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +package io.papermc.paper.registry;
 +
 +import net.kyori.adventure.key.Keyed;
++import org.bukkit.Art;
++import org.bukkit.Fluid;
 +import org.bukkit.GameEvent;
++import org.bukkit.MusicInstrument;
++import org.bukkit.Particle;
++import org.bukkit.Sound;
++import org.bukkit.attribute.Attribute;
 +import org.bukkit.block.Biome;
++import org.bukkit.block.banner.PatternType;
++import org.bukkit.damage.DamageType;
++import org.bukkit.enchantments.Enchantment;
++import org.bukkit.entity.Cat;
++import org.bukkit.entity.EntityType;
++import org.bukkit.entity.Frog;
++import org.bukkit.entity.Villager;
++import org.bukkit.entity.Wolf;
++import org.bukkit.entity.memory.MemoryKey;
 +import org.bukkit.generator.structure.Structure;
 +import org.bukkit.generator.structure.StructureType;
 +import org.bukkit.inventory.meta.trim.TrimMaterial;
 +import org.bukkit.inventory.meta.trim.TrimPattern;
-+import org.jetbrains.annotations.ApiStatus;
++import org.bukkit.map.MapCursor;
++import org.bukkit.potion.PotionEffectType;
++import org.bukkit.potion.PotionType;
 +
 +import static io.papermc.paper.registry.RegistryKeyImpl.create;
 +
@@ -115,7 +132,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 + * @param <T> the value type
 + */
 +@SuppressWarnings("unused")
-+@ApiStatus.Experimental
 +public sealed interface RegistryKey<T> extends Keyed permits RegistryKeyImpl {
 +
 +    /* ******************* *
@@ -131,6 +147,22 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +     * @see io.papermc.paper.registry.keys.StructureTypeKeys
 +     */
 +    RegistryKey<StructureType> STRUCTURE_TYPE = create("worldgen/structure_type");
++    /**
++     * Built-in registry for instruments.
++     * @see io.papermc.paper.registry.keys.InstrumentKeys
++     */
++    RegistryKey<MusicInstrument> INSTRUMENT = create("instrument");
++    /**
++     * Built-in registry for enchantments.
++     * @see io.papermc.paper.registry.keys.EnchantmentKeys
++     */
++    RegistryKey<Enchantment> ENCHANTMENT = create("enchantment");
++    /**
++     * Built-in registry for potion effect types (mob effects).
++     * @see io.papermc.paper.registry.keys.MobEffectKeys
++     */
++    RegistryKey<PotionEffectType> MOB_EFFECT = create("mob_effect");
++
 +
 +    /* ********************** *
 +     * Data-driven Registries *
@@ -155,6 +187,35 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +     * @see io.papermc.paper.registry.keys.TrimPatternKeys
 +     */
 +    RegistryKey<TrimPattern> TRIM_PATTERN = create("trim_pattern");
++    /**
++     * Data-driven registry for damage types.
++     * @see io.papermc.paper.registry.keys.DamageTypeKeys
++     */
++    RegistryKey<DamageType> DAMAGE_TYPE = create("damage_type");
++    /**
++     * Data-driven registry for wolf variants
++     * @see io.papermc.paper.registry.keys.WolfVariantKeys
++     */
++    RegistryKey<Wolf.Variant> WOLF_VARIANT = create("wolf_variant");
++
++
++    /* ******************* *
++     * API-only Registries *
++     * ******************* */
++    RegistryKey<Art> PAINTING_VARIANT = create("painting_variant");
++    RegistryKey<Attribute> ATTRIBUTE = create("attribute");
++    RegistryKey<PatternType> BANNER_PATTERN = create("banner_pattern");
++    RegistryKey<Cat.Type> CAT_VARIANT = create("cat_variant");
++    RegistryKey<EntityType> ENTITY_TYPE = create("entity_type");
++    RegistryKey<Particle> PARTICLE_TYPE = create("particle_type");
++    RegistryKey<PotionType> POTION = create("potion");
++    RegistryKey<Sound> SOUND_EVENT = create("sound_event");
++    RegistryKey<Villager.Profession> VILLAGER_PROFESSION = create("villager_profession");
++    RegistryKey<Villager.Type> VILLAGER_TYPE = create("villager_type");
++    RegistryKey<MemoryKey<?>> MEMORY_MODULE_TYPE = create("memory_module_type");
++    RegistryKey<Fluid> FLUID = create("fluid");
++    RegistryKey<Frog.Variant> FROG_VARIANT = create("frog_variant");
++    RegistryKey<MapCursor.Type> MAP_DECORATION_TYPE = create("map_decoration_type");
 +}
 diff --git a/src/main/java/io/papermc/paper/registry/RegistryKeyImpl.java b/src/main/java/io/papermc/paper/registry/RegistryKeyImpl.java
 new file mode 100644
@@ -175,11 +236,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    static final Set<RegistryKey<?>> REGISTRY_KEYS = Sets.newIdentityHashSet();
 +
 +    static <T> RegistryKey<T> create(@Subst("some_key") final String key) {
-+        final RegistryKey<T> registryKey = new RegistryKeyImpl<>(Key.key(Key.MINECRAFT_NAMESPACE, key));
++        final RegistryKey<T> registryKey = createInternal(key);
 +        REGISTRY_KEYS.add(registryKey);
 +        return registryKey;
 +    }
 +
++    // creates the key without adding to the internal set of keys
++    static <T> RegistryKey<T> createInternal(@Subst("some_key") final String key) {
++        return new RegistryKeyImpl<>(Key.key(Key.MINECRAFT_NAMESPACE, key));
++    }
++
 +}
 diff --git a/src/main/java/io/papermc/paper/registry/TypedKey.java b/src/main/java/io/papermc/paper/registry/TypedKey.java
 new file mode 100644
@@ -227,7 +293,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +     * @return a new key for the value key and registry key
 +     */
 +    @ApiStatus.Experimental
-+    static <T extends Keyed> @NotNull TypedKey<T> create(final @NotNull RegistryKey<T> registryKey, final @NotNull Key key) {
++    static <T> @NotNull TypedKey<T> create(final @NotNull RegistryKey<T> registryKey, final @NotNull Key key) {
 +        return new TypedKeyImpl<>(key, registryKey);
 +    }
 +}
@@ -243,7 +309,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import net.kyori.adventure.key.Keyed;
 +import org.jetbrains.annotations.NotNull;
 +
-+record TypedKeyImpl<T extends Keyed>(@NotNull Key key, @NotNull RegistryKey<T> registryKey) implements TypedKey<T> {
++record TypedKeyImpl<T>(@NotNull Key key, @NotNull RegistryKey<T> registryKey) implements TypedKey<T> {
 +}
 diff --git a/src/main/java/org/bukkit/MinecraftExperimental.java b/src/main/java/org/bukkit/MinecraftExperimental.java
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
diff --git a/patches/api/More-PotionEffectType-API.patch b/patches/api/More-PotionEffectType-API.patch
index 464fc89fe7..af0bd7e71f 100644
--- a/patches/api/More-PotionEffectType-API.patch
+++ b/patches/api/More-PotionEffectType-API.patch
@@ -11,7 +11,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 @@ -0,0 +0,0 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
       */
      @Deprecated(forRemoval = true)
-     Registry<io.papermc.paper.world.structure.ConfiguredStructure> CONFIGURED_STRUCTURE = Bukkit.getRegistry(io.papermc.paper.world.structure.ConfiguredStructure.class);
+     Registry<io.papermc.paper.world.structure.ConfiguredStructure> CONFIGURED_STRUCTURE = Objects.requireNonNull(io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(io.papermc.paper.world.structure.ConfiguredStructure.class), "No registry present for ConfiguredStructure. This is a bug.");
 +
 +    /**
 +     * Potion effect types.
@@ -38,8 +38,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        }
 +    };
      // Paper end
- 
      /**
+      * Get the object by its key.
 diff --git a/src/main/java/org/bukkit/potion/PotionEffectType.java b/src/main/java/org/bukkit/potion/PotionEffectType.java
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/org/bukkit/potion/PotionEffectType.java
diff --git a/patches/server/Add-RegistryAccess-for-managing-Registries.patch b/patches/server/Add-RegistryAccess-for-managing-Registries.patch
new file mode 100644
index 0000000000..a0a71ec078
--- /dev/null
+++ b/patches/server/Add-RegistryAccess-for-managing-Registries.patch
@@ -0,0 +1,1026 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <jake.m.potrebic@gmail.com>
+Date: Mon, 27 Feb 2023 18:28:39 -0800
+Subject: [PATCH] Add RegistryAccess for managing Registries
+
+RegistryAccess is independant from CraftServer and
+doesn't require one to be created allowing the
+org.bukkit.Registry class to be loaded earlier.
+
+== AT ==
+public net.minecraft.server.RegistryLayer STATIC_ACCESS
+
+diff --git a/src/main/java/io/papermc/paper/registry/PaperRegistries.java b/src/main/java/io/papermc/paper/registry/PaperRegistries.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/PaperRegistries.java
+@@ -0,0 +0,0 @@
++package io.papermc.paper.registry;
++
++import io.papermc.paper.registry.entry.RegistryEntry;
++import java.util.Collections;
++import java.util.IdentityHashMap;
++import java.util.List;
++import java.util.Map;
++import net.minecraft.core.Registry;
++import net.minecraft.core.registries.Registries;
++import net.minecraft.resources.ResourceKey;
++import net.minecraft.world.item.enchantment.Enchantment;
++import net.minecraft.world.level.levelgen.structure.Structure;
++import org.bukkit.GameEvent;
++import org.bukkit.Keyed;
++import org.bukkit.MusicInstrument;
++import org.bukkit.craftbukkit.CraftGameEvent;
++import org.bukkit.craftbukkit.CraftMusicInstrument;
++import org.bukkit.craftbukkit.damage.CraftDamageType;
++import org.bukkit.craftbukkit.enchantments.CraftEnchantment;
++import org.bukkit.craftbukkit.entity.CraftWolf;
++import org.bukkit.craftbukkit.generator.structure.CraftStructure;
++import org.bukkit.craftbukkit.generator.structure.CraftStructureType;
++import org.bukkit.craftbukkit.inventory.trim.CraftTrimMaterial;
++import org.bukkit.craftbukkit.inventory.trim.CraftTrimPattern;
++import org.bukkit.craftbukkit.potion.CraftPotionEffectType;
++import org.bukkit.damage.DamageType;
++import org.bukkit.entity.Wolf;
++import org.bukkit.entity.memory.MemoryKey;
++import org.bukkit.generator.structure.StructureType;
++import org.bukkit.inventory.meta.trim.TrimMaterial;
++import org.bukkit.inventory.meta.trim.TrimPattern;
++import org.bukkit.potion.PotionEffectType;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.checker.nullness.qual.Nullable;
++import org.checkerframework.framework.qual.DefaultQualifier;
++
++import static io.papermc.paper.registry.entry.RegistryEntry.apiOnly;
++import static io.papermc.paper.registry.entry.RegistryEntry.entry;
++
++@DefaultQualifier(NonNull.class)
++public final class PaperRegistries {
++
++    static final List<RegistryEntry<?, ?, ?>> REGISTRY_ENTRIES;
++    private static final Map<RegistryKey<?>, RegistryEntry<?, ?, ?>> BY_REGISTRY_KEY;
++    private static final Map<ResourceKey<?>, RegistryEntry<?, ?, ?>> BY_RESOURCE_KEY;
++    static {
++        REGISTRY_ENTRIES = List.of(
++            // built-ins
++            entry(Registries.ENCHANTMENT, RegistryKey.ENCHANTMENT, Enchantment.class, CraftEnchantment::new),
++            entry(Registries.GAME_EVENT, RegistryKey.GAME_EVENT, GameEvent.class, CraftGameEvent::new),
++            entry(Registries.INSTRUMENT, RegistryKey.INSTRUMENT, MusicInstrument.class, CraftMusicInstrument::new),
++            entry(Registries.MOB_EFFECT, RegistryKey.MOB_EFFECT, PotionEffectType.class, CraftPotionEffectType::new),
++            entry(Registries.STRUCTURE_TYPE, RegistryKey.STRUCTURE_TYPE, StructureType.class, CraftStructureType::new),
++
++            // data-drivens
++            entry(Registries.STRUCTURE, RegistryKey.STRUCTURE, Structure.class, CraftStructure::new).delayed(),
++            entry(Registries.TRIM_MATERIAL, RegistryKey.TRIM_MATERIAL, TrimMaterial.class, CraftTrimMaterial::new).delayed(),
++            entry(Registries.TRIM_PATTERN, RegistryKey.TRIM_PATTERN, TrimPattern.class, CraftTrimPattern::new).delayed(),
++            entry(Registries.DAMAGE_TYPE, RegistryKey.DAMAGE_TYPE, DamageType.class, CraftDamageType::new).delayed(),
++            entry(Registries.WOLF_VARIANT, RegistryKey.WOLF_VARIANT, Wolf.Variant.class, CraftWolf.CraftVariant::new).delayed(),
++
++            // api-only
++            apiOnly(Registries.BIOME, RegistryKey.BIOME, () -> org.bukkit.Registry.BIOME),
++            apiOnly(Registries.PAINTING_VARIANT, RegistryKey.PAINTING_VARIANT, () -> org.bukkit.Registry.ART),
++            apiOnly(Registries.ATTRIBUTE, RegistryKey.ATTRIBUTE, () -> org.bukkit.Registry.ATTRIBUTE),
++            apiOnly(Registries.BANNER_PATTERN, RegistryKey.BANNER_PATTERN, () -> org.bukkit.Registry.BANNER_PATTERN),
++            apiOnly(Registries.CAT_VARIANT, RegistryKey.CAT_VARIANT, () -> org.bukkit.Registry.CAT_VARIANT),
++            apiOnly(Registries.ENTITY_TYPE, RegistryKey.ENTITY_TYPE, () -> org.bukkit.Registry.ENTITY_TYPE),
++            apiOnly(Registries.PARTICLE_TYPE, RegistryKey.PARTICLE_TYPE, () -> org.bukkit.Registry.PARTICLE_TYPE),
++            apiOnly(Registries.POTION, RegistryKey.POTION, () -> org.bukkit.Registry.POTION),
++            apiOnly(Registries.SOUND_EVENT, RegistryKey.SOUND_EVENT, () -> org.bukkit.Registry.SOUNDS),
++            apiOnly(Registries.VILLAGER_PROFESSION, RegistryKey.VILLAGER_PROFESSION, () -> org.bukkit.Registry.VILLAGER_PROFESSION),
++            apiOnly(Registries.VILLAGER_TYPE, RegistryKey.VILLAGER_TYPE, () -> org.bukkit.Registry.VILLAGER_TYPE),
++            apiOnly(Registries.MEMORY_MODULE_TYPE, RegistryKey.MEMORY_MODULE_TYPE, () -> (org.bukkit.Registry<MemoryKey<?>>) (org.bukkit.Registry) org.bukkit.Registry.MEMORY_MODULE_TYPE),
++            apiOnly(Registries.FLUID, RegistryKey.FLUID, () -> org.bukkit.Registry.FLUID),
++            apiOnly(Registries.FROG_VARIANT, RegistryKey.FROG_VARIANT, () -> org.bukkit.Registry.FROG_VARIANT),
++            apiOnly(Registries.MAP_DECORATION_TYPE, RegistryKey.MAP_DECORATION_TYPE, () -> org.bukkit.Registry.MAP_DECORATION_TYPE)
++        );
++        final Map<RegistryKey<?>, RegistryEntry<?, ?, ?>> byRegistryKey = new IdentityHashMap<>(REGISTRY_ENTRIES.size());
++        final Map<ResourceKey<?>, RegistryEntry<?, ?, ?>> byResourceKey = new IdentityHashMap<>(REGISTRY_ENTRIES.size());
++        for (final RegistryEntry<?, ?, ?> entry : REGISTRY_ENTRIES) {
++            byRegistryKey.put(entry.apiKey(), entry);
++            byResourceKey.put(entry.mcKey(), entry);
++        }
++        BY_REGISTRY_KEY = Collections.unmodifiableMap(byRegistryKey);
++        BY_RESOURCE_KEY = Collections.unmodifiableMap(byResourceKey);
++    }
++
++    @SuppressWarnings("unchecked")
++    public static <M, T extends Keyed, R extends org.bukkit.Registry<T>> @Nullable RegistryEntry<M, T, R> getEntry(final ResourceKey<? extends Registry<M>> resourceKey) {
++        return (RegistryEntry<M, T, R>) BY_RESOURCE_KEY.get(resourceKey);
++    }
++
++    @SuppressWarnings("unchecked")
++    public static <M, T extends Keyed, R extends org.bukkit.Registry<T>> @Nullable RegistryEntry<M, T, R> getEntry(final RegistryKey<? super T> registryKey) {
++        return (RegistryEntry<M, T, R>) BY_REGISTRY_KEY.get(registryKey);
++    }
++
++    private PaperRegistries() {
++    }
++}
+diff --git a/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java b/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java
+@@ -0,0 +0,0 @@
++package io.papermc.paper.registry;
++
++import io.papermc.paper.registry.entry.ApiRegistryEntry;
++import io.papermc.paper.registry.entry.RegistryEntry;
++import io.papermc.paper.registry.legacy.DelayedRegistry;
++import io.papermc.paper.registry.legacy.DelayedRegistryEntry;
++import io.papermc.paper.registry.legacy.LegacyRegistryIdentifiers;
++import java.util.Map;
++import java.util.NoSuchElementException;
++import java.util.Set;
++import java.util.concurrent.ConcurrentHashMap;
++import java.util.stream.Collectors;
++import net.minecraft.resources.ResourceKey;
++import org.bukkit.Keyed;
++import org.bukkit.Registry;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.checker.nullness.qual.Nullable;
++import org.checkerframework.framework.qual.DefaultQualifier;
++import org.jetbrains.annotations.VisibleForTesting;
++
++import static java.util.Objects.requireNonNull;
++
++@DefaultQualifier(NonNull.class)
++public class PaperRegistryAccess implements RegistryAccess {
++
++    // We store the API registries in a memoized supplier, so they can be created on-demand.
++    // These suppliers are added to this map right after the instance of nms.Registry is created before it is loaded.
++    // We want to do registration there, so we have access to the nms.Registry instance in order to wrap it in a CraftRegistry instance.
++    // The memoized Supplier is needed because we *can't* instantiate any CraftRegistry class until **all** the BuiltInRegistries have been added
++    // to this map because that would class-load org.bukkit.Registry which would query this map.
++    private final Map<RegistryKey<?>, RegistryHolder<?>> registries = new ConcurrentHashMap<>(); // is "identity" because RegistryKey overrides equals and hashCode
++
++    public static PaperRegistryAccess instance() {
++        return (PaperRegistryAccess) RegistryAccessHolder.INSTANCE.orElseThrow(() -> new IllegalStateException("No RegistryAccess implementation found"));
++    }
++
++    @VisibleForTesting
++    public Set<RegistryKey<?>> getLoadedServerBackedRegistries() {
++        return this.registries.keySet().stream().filter(registryHolder -> !(PaperRegistries.getEntry(registryHolder) instanceof ApiRegistryEntry)).collect(Collectors.toUnmodifiableSet());
++    }
++
++    @SuppressWarnings("unchecked")
++    @Deprecated(forRemoval = true)
++    @Override
++    public <T extends Keyed> @Nullable Registry<T> getRegistry(final Class<T> type) {
++        final RegistryKey<T> registryKey;
++        final @Nullable RegistryEntry<?, T, ?> entry;
++        registryKey = requireNonNull(byType(type), () -> type + " is not a valid registry type");
++        entry = PaperRegistries.getEntry(registryKey);
++        final @Nullable RegistryHolder<T> registry = (RegistryHolder<T>) this.registries.get(registryKey);
++        if (registry != null) {
++            // if the registry exists, return right away. Since this is the "legacy" method, we return DelayedRegistry
++            // for the non-builtin Registry instances stored as fields in Registry.
++            return registry.get();
++        } else if (entry instanceof DelayedRegistryEntry<?, T, ?>) {
++            // if the registry doesn't exist and the entry is marked as "delayed", we create a registry holder that is empty
++            // which will later be filled with the actual registry. This is so the fields on org.bukkit.Registry can be populated with
++            // registries that don't exist at the time org.bukkit.Registry is statically initialized.
++            final RegistryHolder<T> delayedHolder = new RegistryHolder.Delayed<>();
++            this.registries.put(registryKey, delayedHolder);
++            return delayedHolder.get();
++        } else {
++            // if the registry doesn't exist yet or doesn't have a delayed entry, just return null
++            return null;
++        }
++    }
++
++    @SuppressWarnings("unchecked")
++    @Override
++    public <T extends Keyed> Registry<T> getRegistry(final RegistryKey<T> key) {
++        if (PaperRegistries.getEntry(key) == null) {
++            throw new NoSuchElementException(key + " is not a valid registry key");
++        }
++        final @Nullable RegistryHolder<T> registryHolder = (RegistryHolder<T>) this.registries.get(key);
++        if (registryHolder == null) {
++            throw new IllegalArgumentException(key + " points to a registry that is not available yet");
++        }
++        // since this is the getRegistry method that uses the modern RegistryKey, we unwrap any DelayedRegistry instances
++        // that might be returned here. I don't think reference equality is required when doing getRegistry(RegistryKey.WOLF_VARIANT) == Registry.WOLF_VARIANT
++        return possiblyUnwrap(registryHolder.get());
++    }
++
++    private static <T extends Keyed> Registry<T> possiblyUnwrap(final Registry<T> registry) {
++        if (registry instanceof final DelayedRegistry<T, ?> delayedRegistry) { // if not coming from legacy, unwrap the delayed registry
++            return delayedRegistry.delegate();
++        }
++        return registry;
++    }
++
++    public <M> void registerReloadableRegistry(final ResourceKey<? extends net.minecraft.core.Registry<M>> resourceKey, final net.minecraft.core.Registry<M> registry) {
++        this.registerRegistry(resourceKey, registry, true);
++    }
++
++    public <M> void registerRegistry(final ResourceKey<? extends net.minecraft.core.Registry<M>> resourceKey, final net.minecraft.core.Registry<M> registry) {
++        this.registerRegistry(resourceKey, registry, false);
++    }
++
++    @SuppressWarnings("unchecked") // this method should be called right after any new MappedRegistry instances are created to later be used by the server.
++    private <M, B extends Keyed, R extends Registry<B>> void registerRegistry(final ResourceKey<? extends net.minecraft.core.Registry<M>> resourceKey, final net.minecraft.core.Registry<M> registry, final boolean replace) {
++        final @Nullable RegistryEntry<M, B, R> entry = PaperRegistries.getEntry(resourceKey);
++        if (entry == null) { // skip registries that don't have API entries
++            return;
++        }
++        final @Nullable RegistryHolder<B> registryHolder = (RegistryHolder<B>) this.registries.get(entry.apiKey());
++        if (registryHolder == null || replace) {
++            // if the holder doesn't exist yet, or is marked as "replaceable", put it in the map.
++            this.registries.put(entry.apiKey(), entry.createRegistryHolder(registry));
++        } else {
++            if (registryHolder instanceof RegistryHolder.Delayed<?, ?> && entry instanceof final DelayedRegistryEntry<M, B, R> delayedEntry) {
++                // if the registry holder is delayed, and the entry is marked as "delayed", then load the holder with the CraftRegistry instance that wraps the actual nms Registry.
++                ((RegistryHolder.Delayed<B, R>) registryHolder).loadFrom(delayedEntry, registry);
++            } else {
++                throw new IllegalArgumentException(resourceKey + " has already been created");
++            }
++        }
++    }
++
++    @SuppressWarnings("unchecked")
++    @Deprecated
++    @VisibleForTesting
++    public static <T extends Keyed> @Nullable RegistryKey<T> byType(final Class<T> type) {
++        return (RegistryKey<T>) LegacyRegistryIdentifiers.CLASS_TO_KEY_MAP.get(type);
++    }
++}
+diff --git a/src/main/java/io/papermc/paper/registry/RegistryHolder.java b/src/main/java/io/papermc/paper/registry/RegistryHolder.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/RegistryHolder.java
+@@ -0,0 +0,0 @@
++package io.papermc.paper.registry;
++
++import com.google.common.base.Suppliers;
++import io.papermc.paper.registry.legacy.DelayedRegistry;
++import io.papermc.paper.registry.legacy.DelayedRegistryEntry;
++import java.util.function.Supplier;
++import org.bukkit.Keyed;
++import org.bukkit.Registry;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.framework.qual.DefaultQualifier;
++
++@DefaultQualifier(NonNull.class)
++public interface RegistryHolder<B extends Keyed> {
++
++    Registry<B> get();
++
++    final class Memoized<B extends Keyed, R extends Registry<B>> implements RegistryHolder<B> {
++
++        private final Supplier<R> memoizedSupplier;
++
++        public Memoized(final Supplier<? extends R> supplier) {
++            this.memoizedSupplier = Suppliers.memoize(supplier::get);
++        }
++
++        public Registry<B> get() {
++            return this.memoizedSupplier.get();
++        }
++    }
++
++    final class Delayed<B extends Keyed, R extends Registry<B>> implements RegistryHolder<B> {
++
++        private final DelayedRegistry<B, R> delayedRegistry = new DelayedRegistry<>();
++
++        @Override
++        public DelayedRegistry<B, R> get() {
++            return this.delayedRegistry;
++        }
++
++        <M> void loadFrom(final DelayedRegistryEntry<M, B, R> delayedEntry, final net.minecraft.core.Registry<M> registry) {
++            final RegistryHolder<B> delegateHolder = delayedEntry.delegate().createRegistryHolder(registry);
++            if (!(delegateHolder instanceof RegistryHolder.Memoized<B, ?>)) {
++                throw new IllegalArgumentException(delegateHolder + " must be a memoized holder");
++            }
++            this.delayedRegistry.load(((Memoized<B, R>) delegateHolder).memoizedSupplier);
++        }
++    }
++}
+diff --git a/src/main/java/io/papermc/paper/registry/entry/ApiRegistryEntry.java b/src/main/java/io/papermc/paper/registry/entry/ApiRegistryEntry.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/entry/ApiRegistryEntry.java
+@@ -0,0 +0,0 @@
++package io.papermc.paper.registry.entry;
++
++import io.papermc.paper.registry.RegistryHolder;
++import io.papermc.paper.registry.RegistryKey;
++import java.util.function.Supplier;
++import net.minecraft.core.Registry;
++import net.minecraft.resources.ResourceKey;
++import org.bukkit.Keyed;
++
++public class ApiRegistryEntry<M, B extends Keyed> extends BaseRegistryEntry<M, B, org.bukkit.Registry<B>> {
++
++    private final Supplier<org.bukkit.Registry<B>> registrySupplier;
++
++    protected ApiRegistryEntry(
++        final ResourceKey<? extends Registry<M>> mcKey,
++        final RegistryKey<B> apiKey,
++        final Supplier<org.bukkit.Registry<B>> registrySupplier
++    ) {
++        super(mcKey, apiKey);
++        this.registrySupplier = registrySupplier;
++    }
++
++    @Override
++    public RegistryHolder<B> createRegistryHolder(final Registry<M> nmsRegistry) {
++        return new RegistryHolder.Memoized<>(this.registrySupplier);
++    }
++}
+diff --git a/src/main/java/io/papermc/paper/registry/entry/BaseRegistryEntry.java b/src/main/java/io/papermc/paper/registry/entry/BaseRegistryEntry.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/entry/BaseRegistryEntry.java
+@@ -0,0 +0,0 @@
++package io.papermc.paper.registry.entry;
++
++import io.papermc.paper.registry.RegistryKey;
++import net.minecraft.core.Registry;
++import net.minecraft.resources.ResourceKey;
++import org.bukkit.Keyed;
++
++public abstract class BaseRegistryEntry<M, B extends Keyed, R extends org.bukkit.Registry<B>> implements RegistryEntry<M, B, R> { // TODO remove Keyed
++
++    private final ResourceKey<? extends Registry<M>> minecraftRegistryKey;
++    private final RegistryKey<B> apiRegistryKey;
++
++    protected BaseRegistryEntry(final ResourceKey<? extends Registry<M>> minecraftRegistryKey, final RegistryKey<B> apiRegistryKey) {
++        this.minecraftRegistryKey = minecraftRegistryKey;
++        this.apiRegistryKey = apiRegistryKey;
++    }
++
++    @Override
++    public final ResourceKey<? extends Registry<M>> mcKey() {
++        return this.minecraftRegistryKey;
++    }
++
++    @Override
++    public final RegistryKey<B> apiKey() {
++        return this.apiRegistryKey;
++    }
++}
+diff --git a/src/main/java/io/papermc/paper/registry/entry/CraftRegistryEntry.java b/src/main/java/io/papermc/paper/registry/entry/CraftRegistryEntry.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/entry/CraftRegistryEntry.java
+@@ -0,0 +0,0 @@
++package io.papermc.paper.registry.entry;
++
++import io.papermc.paper.registry.RegistryHolder;
++import io.papermc.paper.registry.RegistryKey;
++import java.util.function.BiFunction;
++import net.minecraft.core.Registry;
++import net.minecraft.resources.ResourceKey;
++import org.bukkit.Keyed;
++import org.bukkit.NamespacedKey;
++import org.bukkit.craftbukkit.CraftRegistry;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.framework.qual.DefaultQualifier;
++
++@DefaultQualifier(NonNull.class)
++public class CraftRegistryEntry<M, B extends Keyed> extends BaseRegistryEntry<M, B, CraftRegistry<B, M>> { // TODO remove Keyed
++
++    protected final Class<?> classToPreload;
++    protected final BiFunction<NamespacedKey, M, B> minecraftToBukkit;
++
++    protected CraftRegistryEntry(
++        final ResourceKey<? extends Registry<M>> mcKey,
++        final RegistryKey<B> apiKey,
++        final Class<?> classToPreload,
++        final BiFunction<NamespacedKey, M, B> minecraftToBukkit
++    ) {
++        super(mcKey, apiKey);
++        this.classToPreload = classToPreload;
++        this.minecraftToBukkit = minecraftToBukkit;
++    }
++
++    @Override
++    public RegistryHolder<B> createRegistryHolder(final Registry<M> nmsRegistry) {
++        return new RegistryHolder.Memoized<>(() -> this.createApiRegistry(nmsRegistry));
++    }
++
++    private CraftRegistry<B, M> createApiRegistry(final Registry<M> nmsRegistry) {
++        return new CraftRegistry<>(this.classToPreload, nmsRegistry, this.minecraftToBukkit);
++    }
++}
+diff --git a/src/main/java/io/papermc/paper/registry/entry/RegistryEntry.java b/src/main/java/io/papermc/paper/registry/entry/RegistryEntry.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/entry/RegistryEntry.java
+@@ -0,0 +0,0 @@
++package io.papermc.paper.registry.entry;
++
++import io.papermc.paper.registry.RegistryHolder;
++import io.papermc.paper.registry.RegistryKey;
++import io.papermc.paper.registry.legacy.DelayedRegistryEntry;
++import java.util.function.BiFunction;
++import java.util.function.Supplier;
++import net.minecraft.core.Registry;
++import net.minecraft.resources.ResourceKey;
++import org.bukkit.Keyed;
++import org.bukkit.NamespacedKey;
++import org.bukkit.craftbukkit.CraftRegistry;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.framework.qual.DefaultQualifier;
++
++@DefaultQualifier(NonNull.class)
++public interface RegistryEntry<M, B extends Keyed, R extends org.bukkit.Registry<B>> extends RegistryEntryInfo<M, B> { // TODO remove Keyed
++
++    RegistryHolder<B> createRegistryHolder(Registry<M> nmsRegistry);
++
++    /**
++     * This should only be used if the registry instance needs to exist early due to the need
++     * to populate a field in {@link org.bukkit.Registry}. Data-driven registries shouldn't exist
++     * as fields, but instead be obtained via {@link io.papermc.paper.registry.RegistryAccess#getRegistry(RegistryKey)}
++     */
++    @Deprecated
++    default RegistryEntry<M, B, R> delayed() {
++        return new DelayedRegistryEntry<>(this);
++    }
++
++    static <M, B extends Keyed> RegistryEntry<M, B, CraftRegistry<B, M>> entry(
++        final ResourceKey<? extends Registry<M>> mcKey,
++        final RegistryKey<B> apiKey,
++        final Class<?> classToPreload,
++        final BiFunction<NamespacedKey, M, B> minecraftToBukkit
++    ) {
++        return new CraftRegistryEntry<>(mcKey, apiKey, classToPreload, minecraftToBukkit);
++    }
++
++    static <M, B extends Keyed> RegistryEntry<M, B, org.bukkit.Registry<B>> apiOnly(
++        final ResourceKey<? extends Registry<M>> mcKey,
++        final RegistryKey<B> apiKey,
++        final Supplier<org.bukkit.Registry<B>> apiRegistrySupplier
++    ) {
++        return new ApiRegistryEntry<>(mcKey, apiKey, apiRegistrySupplier);
++    }
++}
+diff --git a/src/main/java/io/papermc/paper/registry/entry/RegistryEntryInfo.java b/src/main/java/io/papermc/paper/registry/entry/RegistryEntryInfo.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/entry/RegistryEntryInfo.java
+@@ -0,0 +0,0 @@
++package io.papermc.paper.registry.entry;
++
++import io.papermc.paper.registry.RegistryKey;
++import net.minecraft.core.Registry;
++import net.minecraft.resources.ResourceKey;
++
++public interface RegistryEntryInfo<M, B> {
++
++    ResourceKey<? extends Registry<M>> mcKey();
++
++    RegistryKey<B> apiKey();
++}
+diff --git a/src/main/java/io/papermc/paper/registry/entry/package-info.java b/src/main/java/io/papermc/paper/registry/entry/package-info.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/entry/package-info.java
+@@ -0,0 +0,0 @@
++@DefaultQualifier(NonNull.class)
++package io.papermc.paper.registry.entry;
++
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.framework.qual.DefaultQualifier;
+diff --git a/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistry.java b/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistry.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistry.java
+@@ -0,0 +0,0 @@
++package io.papermc.paper.registry.legacy;
++
++import java.util.Iterator;
++import java.util.function.Supplier;
++import java.util.stream.Stream;
++import org.bukkit.Keyed;
++import org.bukkit.NamespacedKey;
++import org.bukkit.Registry;
++import org.bukkit.craftbukkit.CraftRegistry;
++import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
++import org.checkerframework.checker.nullness.qual.Nullable;
++import org.jetbrains.annotations.NotNull;
++
++/**
++ * This is to support the now-deprecated fields in {@link Registry} for
++ * data-driven registries.
++ */
++public final class DelayedRegistry<T extends Keyed, R extends Registry<T>> implements Registry<T> {
++
++    private @MonotonicNonNull Supplier<? extends R> delegate;
++
++    public void load(final Supplier<? extends R> registry) {
++        if (this.delegate != null) {
++            throw new IllegalStateException("Registry already loaded!");
++        }
++        this.delegate = registry;
++    }
++
++    public Registry<T> delegate() {
++        if (this.delegate == null) {
++            throw new IllegalStateException("You are trying to access this registry too early!");
++        }
++        return this.delegate.get();
++    }
++
++    @Override
++    public @Nullable T get(final NamespacedKey key) {
++        return this.delegate().get(key);
++    }
++
++    @Override
++    public Iterator<T> iterator() {
++        return this.delegate().iterator();
++    }
++
++    @Override
++    public Stream<T> stream() {
++        return this.delegate().stream();
++    }
++
++    @Override
++    public NamespacedKey getKey(final T value) {
++        return this.delegate().getKey(value);
++    }
++}
+diff --git a/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistryEntry.java b/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistryEntry.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistryEntry.java
+@@ -0,0 +0,0 @@
++package io.papermc.paper.registry.legacy;
++
++import io.papermc.paper.registry.RegistryHolder;
++import io.papermc.paper.registry.RegistryKey;
++import io.papermc.paper.registry.entry.RegistryEntry;
++import net.minecraft.core.Registry;
++import net.minecraft.resources.ResourceKey;
++import org.bukkit.Keyed;
++
++public record DelayedRegistryEntry<M, T extends Keyed, R extends org.bukkit.Registry<T>>(RegistryEntry<M, T, R> delegate) implements RegistryEntry<M, T, R> {
++
++    @Override
++    public ResourceKey<? extends Registry<M>> mcKey() {
++        return this.delegate.mcKey();
++    }
++
++    @Override
++    public RegistryKey<T> apiKey() {
++        return this.delegate.apiKey();
++    }
++
++    @Override
++    public RegistryHolder<T> createRegistryHolder(final Registry<M> nmsRegistry) {
++        return this.delegate.createRegistryHolder(nmsRegistry);
++    }
++}
+diff --git a/src/main/java/io/papermc/paper/registry/legacy/LegacyRegistryIdentifiers.java b/src/main/java/io/papermc/paper/registry/legacy/LegacyRegistryIdentifiers.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/legacy/LegacyRegistryIdentifiers.java
+@@ -0,0 +0,0 @@
++package io.papermc.paper.registry.legacy;
++
++import com.google.common.collect.ImmutableMap;
++import io.leangen.geantyref.GenericTypeReflector;
++import io.papermc.paper.registry.RegistryKey;
++import java.lang.reflect.Field;
++import java.lang.reflect.ParameterizedType;
++import java.util.Map;
++
++@Deprecated
++public final class LegacyRegistryIdentifiers {
++
++    public static final Map<Class<?>, RegistryKey<?>> CLASS_TO_KEY_MAP;
++
++    static {
++        final ImmutableMap.Builder<Class<?>, RegistryKey<?>> builder = ImmutableMap.builder();
++        try {
++            for (final Field field : RegistryKey.class.getFields()) {
++                if (field.getType() == RegistryKey.class) {
++                    // get the legacy type from the RegistryKey generic parameter on the field
++                    final Class<?> legacyType = GenericTypeReflector.erase(((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]);
++                    builder.put(legacyType, (RegistryKey<?>) field.get(null));
++                }
++            }
++        } catch (final ReflectiveOperationException ex) {
++            throw new RuntimeException(ex);
++        }
++        CLASS_TO_KEY_MAP = builder.build();
++    }
++
++    private LegacyRegistryIdentifiers() {
++    }
++}
+diff --git a/src/main/java/io/papermc/paper/registry/legacy/package-info.java b/src/main/java/io/papermc/paper/registry/legacy/package-info.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/legacy/package-info.java
+@@ -0,0 +0,0 @@
++@DefaultQualifier(NonNull.class)
++package io.papermc.paper.registry.legacy;
++
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.framework.qual.DefaultQualifier;
+diff --git a/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java b/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java
++++ b/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java
+@@ -0,0 +0,0 @@ public class BuiltInRegistries {
+         ResourceKey<? extends Registry<T>> key, R registry, BuiltInRegistries.RegistryBootstrap<T> initializer
+     ) {
+         Bootstrap.checkBootstrapCalled(() -> "registry " + key);
++        io.papermc.paper.registry.PaperRegistryAccess.instance().registerRegistry(registry.key(), registry); // Paper - initialize API registry
+         ResourceLocation resourceLocation = key.location();
+         LOADERS.put(resourceLocation, () -> initializer.run(registry));
+         WRITABLE_REGISTRY.register((ResourceKey)key, registry, RegistrationInfo.BUILT_IN); // Paper - decompile fix
+diff --git a/src/main/java/net/minecraft/resources/RegistryDataLoader.java b/src/main/java/net/minecraft/resources/RegistryDataLoader.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/resources/RegistryDataLoader.java
++++ b/src/main/java/net/minecraft/resources/RegistryDataLoader.java
+@@ -0,0 +0,0 @@ public class RegistryDataLoader {
+     public static record RegistryData<T>(ResourceKey<? extends Registry<T>> key, Codec<T> elementCodec) {
+         RegistryDataLoader.Loader<T> create(Lifecycle lifecycle, Map<ResourceKey<?>, Exception> errors) {
+             WritableRegistry<T> writableRegistry = new MappedRegistry<>(this.key, lifecycle);
++            io.papermc.paper.registry.PaperRegistryAccess.instance().registerRegistry(this.key, writableRegistry); // Paper - initialize API registry
+             return new RegistryDataLoader.Loader<>(this, writableRegistry, errors);
+         }
+ 
+diff --git a/src/main/java/net/minecraft/server/ReloadableServerRegistries.java b/src/main/java/net/minecraft/server/ReloadableServerRegistries.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/server/ReloadableServerRegistries.java
++++ b/src/main/java/net/minecraft/server/ReloadableServerRegistries.java
+@@ -0,0 +0,0 @@ public class ReloadableServerRegistries {
+         return CompletableFuture.supplyAsync(
+             () -> {
+                 WritableRegistry<T> writableRegistry = new MappedRegistry<>(type.registryKey(), Lifecycle.experimental());
++                io.papermc.paper.registry.PaperRegistryAccess.instance().registerReloadableRegistry(type.registryKey(), writableRegistry); // Paper - register reloadable registry
+                 Map<ResourceLocation, JsonElement> map = new HashMap<>();
+                 SimpleJsonResourceReloadListener.scanDirectory(resourceManager, type.directory(), GSON, map);
+                 map.forEach(
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
+@@ -0,0 +0,0 @@ public class CraftRegistry<B extends Keyed, M> implements Registry<B> {
+                 + ", this can happen if a plugin creates its own registry entry with out properly registering it.");
+     }
+ 
+-    /**
+-     * Note: Newly added registries should also be added to RegistriesArgumentProvider in the test package
+-     *
+-     * @param bukkitClass the bukkit class of the registry
+-     * @param registryHolder the minecraft registry holder
+-     * @return the bukkit registry of the provided class
+-     */
+-    public static <B extends Keyed> Registry<?> createRegistry(Class<B> bukkitClass, RegistryAccess registryHolder) {
+-        if (bukkitClass == Enchantment.class) {
+-            return new CraftRegistry<>(Enchantment.class, registryHolder.registryOrThrow(Registries.ENCHANTMENT), CraftEnchantment::new);
+-        }
+-        if (bukkitClass == GameEvent.class) {
+-            return new CraftRegistry<>(GameEvent.class, registryHolder.registryOrThrow(Registries.GAME_EVENT), CraftGameEvent::new);
+-        }
+-        if (bukkitClass == MusicInstrument.class) {
+-            return new CraftRegistry<>(MusicInstrument.class, registryHolder.registryOrThrow(Registries.INSTRUMENT), CraftMusicInstrument::new);
+-        }
+-        if (bukkitClass == PotionEffectType.class) {
+-            return new CraftRegistry<>(PotionEffectType.class, registryHolder.registryOrThrow(Registries.MOB_EFFECT), CraftPotionEffectType::new);
+-        }
+-        if (bukkitClass == Structure.class) {
+-            return new CraftRegistry<>(Structure.class, registryHolder.registryOrThrow(Registries.STRUCTURE), CraftStructure::new);
+-        }
+-        if (bukkitClass == StructureType.class) {
+-            return new CraftRegistry<>(StructureType.class, BuiltInRegistries.STRUCTURE_TYPE, CraftStructureType::new);
+-        }
+-        if (bukkitClass == TrimMaterial.class) {
+-            return new CraftRegistry<>(TrimMaterial.class, registryHolder.registryOrThrow(Registries.TRIM_MATERIAL), CraftTrimMaterial::new);
+-        }
+-        if (bukkitClass == TrimPattern.class) {
+-            return new CraftRegistry<>(TrimPattern.class, registryHolder.registryOrThrow(Registries.TRIM_PATTERN), CraftTrimPattern::new);
+-        }
+-        if (bukkitClass == DamageType.class) {
+-            return new CraftRegistry<>(DamageType.class, registryHolder.registryOrThrow(Registries.DAMAGE_TYPE), CraftDamageType::new);
+-        }
+-        if (bukkitClass == Wolf.Variant.class) {
+-            return new CraftRegistry<>(Wolf.Variant.class, registryHolder.registryOrThrow(Registries.WOLF_VARIANT), CraftWolf.CraftVariant::new);
+-        }
+-
+-        return null;
+-    }
++    // Paper - move to PaperRegistries
+ 
+-    private final Class<? super B> bukkitClass;
++    private final Class<?> bukkitClass; // Paper - relax preload class
+     private final Map<NamespacedKey, B> cache = new HashMap<>();
+     private final net.minecraft.core.Registry<M> minecraftRegistry;
+     private final BiFunction<NamespacedKey, M, B> minecraftToBukkit;
+     private boolean init;
+ 
+-    public CraftRegistry(Class<? super B> bukkitClass, net.minecraft.core.Registry<M> minecraftRegistry, BiFunction<NamespacedKey, M, B> minecraftToBukkit) {
++    public CraftRegistry(Class<?> bukkitClass, net.minecraft.core.Registry<M> minecraftRegistry, BiFunction<NamespacedKey, M, B> minecraftToBukkit) { // Paper - relax preload class
+         this.bukkitClass = bukkitClass;
+         this.minecraftRegistry = minecraftRegistry;
+         this.minecraftToBukkit = minecraftToBukkit;
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+@@ -0,0 +0,0 @@ public final class CraftServer implements Server {
+     protected final DedicatedServer console;
+     protected final DedicatedPlayerList playerList;
+     private final Map<String, World> worlds = new LinkedHashMap<String, World>();
+-    private final Map<Class<?>, Registry<?>> registries = new HashMap<>();
++    // private final Map<Class<?>, Registry<?>> registries = new HashMap<>(); // Paper - replace with RegistryAccess
+     private YamlConfiguration configuration;
+     private YamlConfiguration commandsConfiguration;
+     private final Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions()));
+@@ -0,0 +0,0 @@ public final class CraftServer implements Server {
+ 
+     @Override
+     public <T extends Keyed> Registry<T> getRegistry(Class<T> aClass) {
+-        return (Registry<T>) this.registries.computeIfAbsent(aClass, key -> CraftRegistry.createRegistry(aClass, this.console.registryAccess()));
++        return io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(aClass); // Paper - replace with RegistryAccess
+     }
+ 
+     @Deprecated
+diff --git a/src/main/resources/META-INF/services/io.papermc.paper.registry.RegistryAccess b/src/main/resources/META-INF/services/io.papermc.paper.registry.RegistryAccess
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/resources/META-INF/services/io.papermc.paper.registry.RegistryAccess
+@@ -0,0 +1 @@
++io.papermc.paper.registry.PaperRegistryAccess
+diff --git a/src/test/java/io/papermc/paper/registry/LegacyRegistryIdentifierTest.java b/src/test/java/io/papermc/paper/registry/LegacyRegistryIdentifierTest.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/test/java/io/papermc/paper/registry/LegacyRegistryIdentifierTest.java
+@@ -0,0 +0,0 @@
++package io.papermc.paper.registry;
++
++import org.bukkit.GameEvent;
++import org.bukkit.MusicInstrument;
++import org.bukkit.inventory.meta.trim.TrimPattern;
++import org.bukkit.support.AbstractTestingBase;
++import org.junit.jupiter.api.Test;
++
++import static org.junit.jupiter.api.Assertions.assertSame;
++
++@Deprecated
++class LegacyRegistryIdentifierTest extends AbstractTestingBase {
++
++    @Test
++    void testSeveralConversions() {
++        assertSame(RegistryKey.GAME_EVENT, PaperRegistryAccess.byType(GameEvent.class));
++        assertSame(RegistryKey.TRIM_PATTERN, PaperRegistryAccess.byType(TrimPattern.class));
++        assertSame(RegistryKey.INSTRUMENT, PaperRegistryAccess.byType(MusicInstrument.class));
++    }
++}
+diff --git a/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java b/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java
++++ b/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java
+@@ -0,0 +0,0 @@
+ package io.papermc.paper.registry;
+ 
++import io.papermc.paper.registry.entry.RegistryEntry;
+ import java.util.Optional;
+ import java.util.stream.Stream;
+ import net.minecraft.core.Registry;
+ import net.minecraft.resources.ResourceKey;
+ import net.minecraft.resources.ResourceLocation;
++import org.bukkit.Keyed;
+ import org.bukkit.support.AbstractTestingBase;
+ import org.junit.jupiter.api.BeforeAll;
+ import org.junit.jupiter.params.ParameterizedTest;
+ import org.junit.jupiter.params.provider.MethodSource;
+ 
++import static org.junit.jupiter.api.Assertions.assertNotNull;
+ import static org.junit.jupiter.api.Assertions.assertTrue;
+ 
+ class RegistryKeyTest extends AbstractTestingBase {
+@@ -0,0 +0,0 @@ class RegistryKeyTest extends AbstractTestingBase {
+     void testApiRegistryKeysExist(final RegistryKey<?> key) {
+         final Optional<Registry<Object>> registry = AbstractTestingBase.REGISTRY_CUSTOM.registry(ResourceKey.createRegistryKey(new ResourceLocation(key.key().asString())));
+         assertTrue(registry.isPresent(), "Missing vanilla registry for " + key.key().asString());
++    }
+ 
++    @ParameterizedTest
++    @MethodSource("data")
++    void testRegistryEntryExists(final RegistryKey<?> key) {
++        final RegistryEntry<?, ?, ?> entry = PaperRegistries.getEntry(key);
++        assertNotNull(entry, "Missing PaperRegistries entry for " + key);
+     }
+ }
+diff --git a/src/test/java/org/bukkit/registry/RegistryArgumentAddedTest.java b/src/test/java/org/bukkit/registry/RegistryArgumentAddedTest.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/test/java/org/bukkit/registry/RegistryArgumentAddedTest.java
++++ b/src/test/java/org/bukkit/registry/RegistryArgumentAddedTest.java
+@@ -0,0 +0,0 @@ public class RegistryArgumentAddedTest extends AbstractTestingBase {
+         // Make sure every registry is created
+         Class.forName(Registry.class.getName());
+ 
+-        Set<Class<?>> loadedRegistries = new HashSet<>(DummyServer.registers.keySet());
+-        Set<Class<?>> notFound = new HashSet<>();
++        // Paper start
++        Set<io.papermc.paper.registry.RegistryKey<?>> loadedRegistries = java.util.Collections.newSetFromMap(new java.util.IdentityHashMap<>());
++        loadedRegistries.addAll(io.papermc.paper.registry.PaperRegistryAccess.instance().getLoadedServerBackedRegistries());
++        // Paper end
++        Set<io.papermc.paper.registry.RegistryKey<?>> notFound = new HashSet<>(); // Paper
+ 
+         RegistriesArgumentProvider
+                 .getData()
+                 .map(Arguments::get)
+                 .map(array -> array[0])
+-                .map(clazz -> (Class<?>) clazz)
++                .map(clazz -> (io.papermc.paper.registry.RegistryKey<?>) clazz) // Paper
+                 .forEach(clazz -> {
+                     if (!loadedRegistries.remove(clazz)) {
+                         notFound.add(clazz);
+diff --git a/src/test/java/org/bukkit/registry/RegistryConversionTest.java b/src/test/java/org/bukkit/registry/RegistryConversionTest.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/test/java/org/bukkit/registry/RegistryConversionTest.java
++++ b/src/test/java/org/bukkit/registry/RegistryConversionTest.java
+@@ -0,0 +0,0 @@ public class RegistryConversionTest extends AbstractTestingBase {
+ 
+     @Order(1)
+     @RegistriesTest
+-    public void testHandleableImplementation(Class<? extends Keyed> clazz) {
++    public void testHandleableImplementation(io.papermc.paper.registry.RegistryKey<? extends Keyed> type, Class<? extends Keyed> clazz) { // Paper
+         Set<Class<? extends Keyed>> notImplemented = new HashSet<>();
+-        Registry<? extends Keyed> registry = Bukkit.getRegistry(clazz);
++        Registry<? extends Keyed> registry = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(type); // Paper
+ 
+         for (Keyed item : registry) {
+             if (!(item instanceof Handleable<?>)) {
+@@ -0,0 +0,0 @@ public class RegistryConversionTest extends AbstractTestingBase {
+ 
+     @Order(2)
+     @RegistriesTest
+-    public void testMinecraftToBukkitPresent(Class<? extends Keyed> clazz, ResourceKey<net.minecraft.core.Registry<?>> registryKey,
++    public void testMinecraftToBukkitPresent(io.papermc.paper.registry.RegistryKey<? extends Keyed> type, Class<? extends Keyed> clazz, ResourceKey<net.minecraft.core.Registry<?>> registryKey, // Paper
+                                              Class<? extends Keyed> craftClazz, Class<?> minecraftClazz) {
+         Method method = null;
+         try {
+@@ -0,0 +0,0 @@ public class RegistryConversionTest extends AbstractTestingBase {
+ 
+     @Order(2)
+     @RegistriesTest
+-    public void testBukkitToMinecraftPresent(Class<? extends Keyed> clazz, ResourceKey<net.minecraft.core.Registry<?>> registryKey,
++    public void testBukkitToMinecraftPresent(io.papermc.paper.registry.RegistryKey<? extends Keyed> type, Class<? extends Keyed> clazz, ResourceKey<net.minecraft.core.Registry<?>> registryKey, // Paper
+                                              Class<? extends Keyed> craftClazz, Class<?> minecraftClazz) {
+         Method method = null;
+         try {
+@@ -0,0 +0,0 @@ public class RegistryConversionTest extends AbstractTestingBase {
+                 """, minecraftClazz.getName(), clazz.getSimpleName());
+     }
+ 
+-    @Order(2)
++    @Order(3)
+     @RegistriesTest
+-    public void testMinecraftToBukkitNullValue(Class<? extends Keyed> clazz) throws IllegalAccessException {
++    public void testMinecraftToBukkitNullValue(io.papermc.paper.registry.RegistryKey<? extends Keyed> type, Class<? extends Keyed> clazz) throws IllegalAccessException { // Paper
+         this.checkValidMinecraftToBukkit(clazz);
+ 
+         try {
+@@ -0,0 +0,0 @@ public class RegistryConversionTest extends AbstractTestingBase {
+ 
+     @Order(3)
+     @RegistriesTest
+-    public void testBukkitToMinecraftNullValue(Class<? extends Keyed> clazz) throws IllegalAccessException {
++    public void testBukkitToMinecraftNullValue(io.papermc.paper.registry.RegistryKey<? extends Keyed> type, Class<? extends Keyed> clazz) throws IllegalAccessException { // Paper
+         this.checkValidBukkitToMinecraft(clazz);
+ 
+         try {
+@@ -0,0 +0,0 @@ public class RegistryConversionTest extends AbstractTestingBase {
+ 
+     @Order(3)
+     @RegistriesTest
+-    public void testMinecraftToBukkit(Class<? extends Keyed> clazz) {
++    public void testMinecraftToBukkit(io.papermc.paper.registry.RegistryKey<? extends Keyed> type, Class<? extends Keyed> clazz) { // Paper
+         this.checkValidMinecraftToBukkit(clazz);
+         this.checkValidHandle(clazz);
+ 
+         Map<Object, Object> notMatching = new HashMap<>();
+         Method method = RegistryConversionTest.MINECRAFT_TO_BUKKIT_METHODS.get(clazz);
+ 
+-        RegistryArgumentProvider.getValues(clazz).map(Arguments::get).forEach(arguments -> {
++        RegistryArgumentProvider.getValues(type).map(Arguments::get).forEach(arguments -> { // Paper
+             Keyed bukkit = (Keyed) arguments[0];
+             Object minecraft = arguments[1];
+ 
+@@ -0,0 +0,0 @@ public class RegistryConversionTest extends AbstractTestingBase {
+ 
+     @Order(3)
+     @RegistriesTest
+-    public void testBukkitToMinecraft(Class<? extends Keyed> clazz) {
++    public void testBukkitToMinecraft(io.papermc.paper.registry.RegistryKey<? extends Keyed> type, Class<? extends Keyed> clazz) { // Paper
+         this.checkValidBukkitToMinecraft(clazz);
+         this.checkValidHandle(clazz);
+ 
+         Map<Object, Object> notMatching = new HashMap<>();
+         Method method = RegistryConversionTest.BUKKIT_TO_MINECRAFT_METHODS.get(clazz);
+ 
+-        RegistryArgumentProvider.getValues(clazz).map(Arguments::get).forEach(arguments -> {
++        RegistryArgumentProvider.getValues(type).map(Arguments::get).forEach(arguments -> { // Paper
+             Keyed bukkit = (Keyed) arguments[0];
+             Object minecraft = arguments[1];
+ 
+@@ -0,0 +0,0 @@ public class RegistryConversionTest extends AbstractTestingBase {
+      */
+     @Order(3)
+     @RegistriesTest
+-    public void testMinecraftToBukkitNoValidMinecraft(Class<? extends Keyed> clazz, ResourceKey<net.minecraft.core.Registry<?>> registryKey,
++    public void testMinecraftToBukkitNoValidMinecraft(io.papermc.paper.registry.RegistryKey<? extends Keyed> type, Class<? extends Keyed> clazz, ResourceKey<net.minecraft.core.Registry<?>> registryKey, // Paper
+                                                       Class<? extends Keyed> craftClazz, Class<?> minecraftClazz) throws IllegalAccessException {
+         this.checkValidMinecraftToBukkit(clazz);
+ 
+diff --git a/src/test/java/org/bukkit/support/DummyServer.java b/src/test/java/org/bukkit/support/DummyServer.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/test/java/org/bukkit/support/DummyServer.java
++++ b/src/test/java/org/bukkit/support/DummyServer.java
+@@ -0,0 +0,0 @@ public final class DummyServer {
+             when(instance.getLootTable(any())).then(mock -> new CraftLootTable(mock.getArgument(0),
+                     AbstractTestingBase.DATA_PACK.fullRegistries().getLootTable(ResourceKey.create(Registries.LOOT_TABLE, CraftNamespacedKey.toMinecraft(mock.getArgument(0))))));
+ 
+-            when(instance.getRegistry(any())).then((Answer<Registry<?>>) mock -> {
+-                Class<? extends Keyed> aClass = mock.getArgument(0);
+-                return registers.computeIfAbsent(aClass, key -> CraftRegistry.createRegistry(aClass, AbstractTestingBase.REGISTRY_CUSTOM));
+-            });
++            // Paper - RegistryAccess
+ 
+             // Paper start - testing additions
+             final Thread currentThread = Thread.currentThread();
+diff --git a/src/test/java/org/bukkit/support/provider/RegistriesArgumentProvider.java b/src/test/java/org/bukkit/support/provider/RegistriesArgumentProvider.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/test/java/org/bukkit/support/provider/RegistriesArgumentProvider.java
++++ b/src/test/java/org/bukkit/support/provider/RegistriesArgumentProvider.java
+@@ -0,0 +0,0 @@
+ package org.bukkit.support.provider;
+ 
+ import com.google.common.collect.Lists;
++import io.papermc.paper.registry.RegistryKey;
+ import java.util.List;
+ import java.util.stream.Stream;
+ import net.minecraft.core.registries.Registries;
+@@ -0,0 +0,0 @@ public class RegistriesArgumentProvider implements ArgumentsProvider {
+ 
+     static {
+         // Order: Bukkit class, Minecraft Registry key, CraftBukkit class, Minecraft class
+-        DATA.add(Arguments.of(Enchantment.class, Registries.ENCHANTMENT, CraftEnchantment.class, net.minecraft.world.item.enchantment.Enchantment.class));
+-        DATA.add(Arguments.of(GameEvent.class, Registries.GAME_EVENT, CraftGameEvent.class, net.minecraft.world.level.gameevent.GameEvent.class));
+-        DATA.add(Arguments.of(MusicInstrument.class, Registries.INSTRUMENT, CraftMusicInstrument.class, Instrument.class));
+-        DATA.add(Arguments.of(PotionEffectType.class, Registries.MOB_EFFECT, CraftPotionEffectType.class, MobEffect.class));
+-        DATA.add(Arguments.of(Structure.class, Registries.STRUCTURE, CraftStructure.class, net.minecraft.world.level.levelgen.structure.Structure.class));
+-        DATA.add(Arguments.of(StructureType.class, Registries.STRUCTURE_TYPE, CraftStructureType.class, net.minecraft.world.level.levelgen.structure.StructureType.class));
+-        DATA.add(Arguments.of(TrimMaterial.class, Registries.TRIM_MATERIAL, CraftTrimMaterial.class, net.minecraft.world.item.armortrim.TrimMaterial.class));
+-        DATA.add(Arguments.of(TrimPattern.class, Registries.TRIM_PATTERN, CraftTrimPattern.class, net.minecraft.world.item.armortrim.TrimPattern.class));
+-        DATA.add(Arguments.of(DamageType.class, Registries.DAMAGE_TYPE, CraftDamageType.class, net.minecraft.world.damagesource.DamageType.class));
+-        DATA.add(Arguments.of(Wolf.Variant.class, Registries.WOLF_VARIANT, CraftWolf.CraftVariant.class, WolfVariant.class));
++        // Order: RegistryKey, Bukkit class, Minecraft Registry key, CraftBukkit class, Minecraft class
++        DATA.add(Arguments.of(RegistryKey.ENCHANTMENT, Enchantment.class, Registries.ENCHANTMENT, CraftEnchantment.class, net.minecraft.world.item.enchantment.Enchantment.class));
++        DATA.add(Arguments.of(RegistryKey.GAME_EVENT, GameEvent.class, Registries.GAME_EVENT, CraftGameEvent.class, net.minecraft.world.level.gameevent.GameEvent.class));
++        DATA.add(Arguments.of(RegistryKey.INSTRUMENT, MusicInstrument.class, Registries.INSTRUMENT, CraftMusicInstrument.class, Instrument.class));
++        DATA.add(Arguments.of(RegistryKey.MOB_EFFECT, PotionEffectType.class, Registries.MOB_EFFECT, CraftPotionEffectType.class, MobEffect.class));
++        DATA.add(Arguments.of(RegistryKey.STRUCTURE, Structure.class, Registries.STRUCTURE, CraftStructure.class, net.minecraft.world.level.levelgen.structure.Structure.class));
++        DATA.add(Arguments.of(RegistryKey.STRUCTURE_TYPE, StructureType.class, Registries.STRUCTURE_TYPE, CraftStructureType.class, net.minecraft.world.level.levelgen.structure.StructureType.class));
++        DATA.add(Arguments.of(RegistryKey.TRIM_MATERIAL, TrimMaterial.class, Registries.TRIM_MATERIAL, CraftTrimMaterial.class, net.minecraft.world.item.armortrim.TrimMaterial.class));
++        DATA.add(Arguments.of(RegistryKey.TRIM_PATTERN, TrimPattern.class, Registries.TRIM_PATTERN, CraftTrimPattern.class, net.minecraft.world.item.armortrim.TrimPattern.class));
++        DATA.add(Arguments.of(RegistryKey.DAMAGE_TYPE, DamageType.class, Registries.DAMAGE_TYPE, CraftDamageType.class, net.minecraft.world.damagesource.DamageType.class));
++        DATA.add(Arguments.of(RegistryKey.WOLF_VARIANT, Wolf.Variant.class, Registries.WOLF_VARIANT, CraftWolf.CraftVariant.class, net.minecraft.world.entity.animal.WolfVariant.class));
+     }
+ 
+     @Override
+diff --git a/src/test/java/org/bukkit/support/provider/RegistryArgumentProvider.java b/src/test/java/org/bukkit/support/provider/RegistryArgumentProvider.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/test/java/org/bukkit/support/provider/RegistryArgumentProvider.java
++++ b/src/test/java/org/bukkit/support/provider/RegistryArgumentProvider.java
+@@ -0,0 +0,0 @@ public class RegistryArgumentProvider implements ArgumentsProvider, AnnotationCo
+ 
+     @Override
+     public Stream<? extends Arguments> provideArguments(ExtensionContext extensionContext) throws Exception {
+-        return RegistryArgumentProvider.getValues(this.registryType);
++        return RegistryArgumentProvider.getValues(io.papermc.paper.registry.PaperRegistryAccess.byType(this.registryType)); // Paper
+     }
+ 
+-    public static Stream<? extends Arguments> getValues(Class<? extends Keyed> registryType) {
+-        Registry<?> registry = Bukkit.getRegistry(registryType);
++    public static Stream<? extends Arguments> getValues(io.papermc.paper.registry.RegistryKey<? extends Keyed> registryType) { // Paper
++        Registry<?> registry = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(registryType); // Paper
+         return registry.stream().map(keyed -> (Handleable<?>) keyed)
+                 .map(handleAble -> Arguments.of(handleAble, handleAble.getHandle()));
+     }
diff --git a/patches/server/Add-StructuresLocateEvent.patch b/patches/server/Add-StructuresLocateEvent.patch
index 087c8249ad..8ca547c3f5 100644
--- a/patches/server/Add-StructuresLocateEvent.patch
+++ b/patches/server/Add-StructuresLocateEvent.patch
@@ -5,6 +5,43 @@ Subject: [PATCH] Add StructuresLocateEvent
 
 Co-authored-by: Jake Potrebic <jake.m.potrebic@gmail.com>
 
+diff --git a/src/main/java/io/papermc/paper/registry/PaperRegistries.java b/src/main/java/io/papermc/paper/registry/PaperRegistries.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/io/papermc/paper/registry/PaperRegistries.java
++++ b/src/main/java/io/papermc/paper/registry/PaperRegistries.java
+@@ -0,0 +0,0 @@ import static io.papermc.paper.registry.entry.RegistryEntry.entry;
+ @DefaultQualifier(NonNull.class)
+ public final class PaperRegistries {
+ 
++    @Deprecated(forRemoval = true)
++    @org.jetbrains.annotations.VisibleForTesting
++    public static final RegistryKey<io.papermc.paper.world.structure.ConfiguredStructure> CONFIGURED_STRUCTURE_REGISTRY_KEY = RegistryKeyImpl.createInternal("worldgen/structure");
++    @Deprecated(forRemoval = true)
++    static final RegistryEntry<Structure, io.papermc.paper.world.structure.ConfiguredStructure, ?> CONFIGURED_STRUCTURE_REGISTRY_ENTRY = entry(Registries.STRUCTURE, CONFIGURED_STRUCTURE_REGISTRY_KEY, io.papermc.paper.world.structure.ConfiguredStructure.class, io.papermc.paper.world.structure.PaperConfiguredStructure::minecraftToBukkit).delayed();
++
+     static final List<RegistryEntry<?, ?, ?>> REGISTRY_ENTRIES;
+     private static final Map<RegistryKey<?>, RegistryEntry<?, ?, ?>> BY_REGISTRY_KEY;
+     private static final Map<ResourceKey<?>, RegistryEntry<?, ?, ?>> BY_RESOURCE_KEY;
+diff --git a/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java b/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java
++++ b/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java
+@@ -0,0 +0,0 @@ public class PaperRegistryAccess implements RegistryAccess {
+     public <T extends Keyed> @Nullable Registry<T> getRegistry(final Class<T> type) {
+         final RegistryKey<T> registryKey;
+         final @Nullable RegistryEntry<?, T, ?> entry;
+-        registryKey = requireNonNull(byType(type), () -> type + " is not a valid registry type");
+-        entry = PaperRegistries.getEntry(registryKey);
++        if (type == io.papermc.paper.world.structure.ConfiguredStructure.class) { // manually handle "duplicate" registries to avoid polluting maps in PaperRegistries
++            registryKey = (RegistryKey<T>) PaperRegistries.CONFIGURED_STRUCTURE_REGISTRY_KEY;
++            entry = (RegistryEntry<?, T, ?>) PaperRegistries.CONFIGURED_STRUCTURE_REGISTRY_ENTRY;
++        } else {
++            registryKey = requireNonNull(byType(type), () -> type + " is not a valid registry type");
++            entry = PaperRegistries.getEntry(registryKey);
++        }
+         final @Nullable RegistryHolder<T> registry = (RegistryHolder<T>) this.registries.get(registryKey);
+         if (registry != null) {
+             // if the registry exists, return right away. Since this is the "legacy" method, we return DelayedRegistry
 diff --git a/src/main/java/io/papermc/paper/world/structure/PaperConfiguredStructure.java b/src/main/java/io/papermc/paper/world/structure/PaperConfiguredStructure.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
@@ -32,18 +69,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    private PaperConfiguredStructure() {
 +    }
 +
-+    @Deprecated(forRemoval = true)
-+    public static final class LegacyRegistry extends CraftRegistry<ConfiguredStructure, Structure> {
-+
-+        public LegacyRegistry(final Registry<Structure> minecraftRegistry) {
-+            super(ConfiguredStructure.class, minecraftRegistry, LegacyRegistry::minecraftToBukkit);
-+        }
-+
-+        private static @Nullable ConfiguredStructure minecraftToBukkit(NamespacedKey key, Structure nms) {
-+            final ResourceLocation structureTypeLoc = Objects.requireNonNull(BuiltInRegistries.STRUCTURE_TYPE.getKey(nms.type()), "unexpected structure type " + nms.type());
-+            final @Nullable StructureType structureType = StructureType.getStructureTypes().get(structureTypeLoc.getPath());
-+            return structureType == null ? null : new ConfiguredStructure(key, structureType);
-+        }
++    public static @Nullable ConfiguredStructure minecraftToBukkit(NamespacedKey key, Structure nms) {
++        final ResourceLocation structureTypeLoc = Objects.requireNonNull(BuiltInRegistries.STRUCTURE_TYPE.getKey(nms.type()), "unexpected structure type " + nms.type());
++        final @Nullable StructureType structureType = StructureType.getStructureTypes().get(structureTypeLoc.getPath());
++        return structureType == null ? null : new ConfiguredStructure(key, structureType);
 +    }
 +}
 diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java
@@ -75,22 +104,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          ChunkGeneratorStructureState chunkgeneratorstructurestate = world.getChunkSource().getGeneratorState();
          Map<StructurePlacement, Set<Holder<Structure>>> map = new Object2ObjectArrayMap();
          Iterator iterator = structures.iterator();
-diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
-+++ b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
-@@ -0,0 +0,0 @@ public class CraftRegistry<B extends Keyed, M> implements Registry<B> {
-             return new CraftRegistry<>(Wolf.Variant.class, registryHolder.registryOrThrow(Registries.WOLF_VARIANT), CraftWolf.CraftVariant::new);
-         }
-         // TODO registry modification API
-+        // Paper start - remove this after a while along with all ConfiguredStructure stuff
-+        if (bukkitClass == io.papermc.paper.world.structure.ConfiguredStructure.class) {
-+            return new io.papermc.paper.world.structure.PaperConfiguredStructure.LegacyRegistry(registryHolder.registryOrThrow(Registries.STRUCTURE));
-+        }
-+        // Paper end
- 
-         return null;
-     }
 diff --git a/src/test/java/io/papermc/paper/world/structure/ConfiguredStructureTest.java b/src/test/java/io/papermc/paper/world/structure/ConfiguredStructureTest.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
@@ -201,7 +214,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                  if (!(object instanceof CraftRegistry<?, ?> registry)) {
                      continue;
                  }
-+                if (object instanceof io.papermc.paper.world.structure.PaperConfiguredStructure.LegacyRegistry) continue; // Paper - skip
++                if (object == Registry.CONFIGURED_STRUCTURE) continue; // Paper - skip
  
                  data.add(Arguments.of(registry));
              } catch (ReflectiveOperationException e) {
@@ -210,10 +223,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 --- a/src/test/java/org/bukkit/registry/RegistryArgumentAddedTest.java
 +++ b/src/test/java/org/bukkit/registry/RegistryArgumentAddedTest.java
 @@ -0,0 +0,0 @@ public class RegistryArgumentAddedTest extends AbstractTestingBase {
- 
-         Set<Class<?>> loadedRegistries = new HashSet<>(DummyServer.registers.keySet());
-         Set<Class<?>> notFound = new HashSet<>();
-+        loadedRegistries.remove(io.papermc.paper.world.structure.ConfiguredStructure.class); // Paper - ignore
+         loadedRegistries.addAll(io.papermc.paper.registry.PaperRegistryAccess.instance().getLoadedServerBackedRegistries());
+         // Paper end
+         Set<io.papermc.paper.registry.RegistryKey<?>> notFound = new HashSet<>(); // Paper
++        loadedRegistries.remove(io.papermc.paper.registry.PaperRegistries.CONFIGURED_STRUCTURE_REGISTRY_KEY); // Paper - ignore
  
          RegistriesArgumentProvider
                  .getData()
diff --git a/patches/server/Flat-bedrock-generator-settings.patch b/patches/server/Flat-bedrock-generator-settings.patch
index aab963184f..505f06e3ea 100644
--- a/patches/server/Flat-bedrock-generator-settings.patch
+++ b/patches/server/Flat-bedrock-generator-settings.patch
@@ -113,9 +113,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                      // Paper start
                      BuiltInRegistries.bootStrap(() -> {
 +                        io.papermc.paper.world.worldgen.OptionallyFlatBedrockConditionSource.bootstrap(); // Paper - Flat bedrock generator settings
-                         io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler.enterBootstrappers(); // Paper - Entrypoint for bootstrapping
                      });
                      // Paper end
+                     CreativeModeTabs.validate();
 diff --git a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java
diff --git a/patches/server/Improve-Registry.patch b/patches/server/Improve-Registry.patch
index b7d03a9dc2..5fd373a658 100644
--- a/patches/server/Improve-Registry.patch
+++ b/patches/server/Improve-Registry.patch
@@ -10,7 +10,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +++ b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
 @@ -0,0 +0,0 @@ public class CraftRegistry<B extends Keyed, M> implements Registry<B> {
  
-     private final Class<? super B> bukkitClass;
+     private final Class<?> bukkitClass; // Paper - relax preload class
      private final Map<NamespacedKey, B> cache = new HashMap<>();
 +    private final Map<B, NamespacedKey> byValue = new java.util.IdentityHashMap<>(); // Paper - improve Registry
      private final net.minecraft.core.Registry<M> minecraftRegistry;
diff --git a/patches/server/Paper-Plugins.patch b/patches/server/Paper-Plugins.patch
index 761cc4df4f..bfb73630ca 100644
--- a/patches/server/Paper-Plugins.patch
+++ b/patches/server/Paper-Plugins.patch
@@ -7211,6 +7211,14 @@ diff --git a/src/main/java/net/minecraft/server/Bootstrap.java b/src/main/java/n
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/net/minecraft/server/Bootstrap.java
 +++ b/src/main/java/net/minecraft/server/Bootstrap.java
+@@ -0,0 +0,0 @@ public class Bootstrap {
+             Bootstrap.isBootstrapped = true;
+             Instant instant = Instant.now();
+ 
++            io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler.enterBootstrappers(); // Paper - Entrypoint for bootstrapping
+             if (BuiltInRegistries.REGISTRY.keySet().isEmpty()) {
+                 throw new IllegalStateException("Unable to load registries");
+             } else {
 @@ -0,0 +0,0 @@ public class Bootstrap {
                      EntitySelectorOptions.bootStrap();
                      DispenseItemBehavior.bootStrap();
@@ -7218,7 +7226,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 -                    BuiltInRegistries.bootStrap();
 +                    // Paper start
 +                    BuiltInRegistries.bootStrap(() -> {
-+                        io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler.enterBootstrappers(); // Paper - Entrypoint for bootstrapping
 +                    });
 +                    // Paper end
                      CreativeModeTabs.validate();
diff --git a/patches/server/TODO-Registry-Modification-API.patch b/patches/server/TODO-Registry-Modification-API.patch
deleted file mode 100644
index 42fac961f7..0000000000
--- a/patches/server/TODO-Registry-Modification-API.patch
+++ /dev/null
@@ -1,18 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Jake Potrebic <jake.m.potrebic@gmail.com>
-Date: Mon, 27 Feb 2023 18:28:39 -0800
-Subject: [PATCH] TODO Registry Modification API
-
-
-diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
-+++ b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
-@@ -0,0 +0,0 @@ public class CraftRegistry<B extends Keyed, M> implements Registry<B> {
-         if (bukkitClass == Wolf.Variant.class) {
-             return new CraftRegistry<>(Wolf.Variant.class, registryHolder.registryOrThrow(Registries.WOLF_VARIANT), CraftWolf.CraftVariant::new);
-         }
-+        // TODO registry modification API
- 
-         return null;
-     }