diff --git a/.editorconfig b/.editorconfig
index 9c12172107..65898b5181 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -31,6 +31,11 @@ ij_java_generate_final_locals = true
 ij_java_generate_final_parameters = true
 ij_java_method_parameters_new_line_after_left_paren = true
 ij_java_method_parameters_right_paren_on_new_line = true
+ij_java_use_fq_class_names = false
+ij_java_class_names_in_javadoc = 1
+
+[paper-server/src/minecraft/java/**/*.java]
+ij_java_use_fq_class_names = true
 
 [paper-server/src/minecraft/resources/data/**/*.json]
 indent_size = 2
diff --git a/paper-server/patches/sources/net/minecraft/core/registries/BuiltInRegistries.java.patch b/paper-server/patches/sources/net/minecraft/core/registries/BuiltInRegistries.java.patch
index bc224c349b..36c52e0a7a 100644
--- a/paper-server/patches/sources/net/minecraft/core/registries/BuiltInRegistries.java.patch
+++ b/paper-server/patches/sources/net/minecraft/core/registries/BuiltInRegistries.java.patch
@@ -26,7 +26,7 @@
          ResourceLocation resourceLocation = key.location();
          LOADERS.put(resourceLocation, () -> bootstrap.run(registry));
          WRITABLE_REGISTRY.register((ResourceKey)key, registry, RegistrationInfo.BUILT_IN);
-@@ -328,7 +_,14 @@
+@@ -328,16 +_,34 @@
      }
  
      public static void bootStrap() {
@@ -41,6 +41,26 @@
          freeze();
          validate(REGISTRY);
      }
+ 
+     private static void createContents() {
++        // Paper start - class-load org.bukkit.Registry
++        // we have to class-load Registry here to create all the CraftRegistry instances
++        // that will be created when Registry is class-loaded before RegistryAccess#getRegistry
++        // would try to create them in lockReferenceHolder
++        try {
++            Class.forName(org.bukkit.Registry.class.getName());
++        } catch (final ClassNotFoundException ex) {
++            throw new RuntimeException(ex);
++        }
++        // Paper end - class-load org.bukkit.Registry
+         LOADERS.forEach((resourceLocation, supplier) -> {
+             if (supplier.get() == null) {
+                 LOGGER.error("Unable to bootstrap registry '{}'", resourceLocation);
+             }
++            io.papermc.paper.registry.PaperRegistryAccess.instance().lockReferenceHolders(ResourceKey.createRegistryKey(resourceLocation)); // Paper - lock reference holder creation
+         });
+     }
+ 
 @@ -346,6 +_,7 @@
  
          for (Registry<?> registry : REGISTRY) {
diff --git a/paper-server/patches/sources/net/minecraft/resources/RegistryDataLoader.java.patch b/paper-server/patches/sources/net/minecraft/resources/RegistryDataLoader.java.patch
index c625d649d2..97a14239e6 100644
--- a/paper-server/patches/sources/net/minecraft/resources/RegistryDataLoader.java.patch
+++ b/paper-server/patches/sources/net/minecraft/resources/RegistryDataLoader.java.patch
@@ -34,11 +34,12 @@
              } catch (Exception var14) {
                  loadingErrors.put(
                      resourceKey,
-@@ -283,7 +_,8 @@
+@@ -283,7 +_,9 @@
              }
          }
  
 -        TagLoader.loadTagsForRegistry(resourceManager, registry);
++        io.papermc.paper.registry.PaperRegistryAccess.instance().lockReferenceHolders(registry.key()); // Paper - lock reference holders
 +        io.papermc.paper.registry.PaperRegistryListenerManager.INSTANCE.runFreezeListeners(registry.key(), conversions); // Paper - run pre-freeze listeners
 +        TagLoader.loadTagsForRegistry(resourceManager, registry, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.INITIAL); // Paper - tag lifecycle - add cause
      }
diff --git a/paper-server/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java b/paper-server/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java
index 3be7e0a1fb..ab8fba3442 100644
--- a/paper-server/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java
+++ b/paper-server/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java
@@ -13,6 +13,7 @@ import java.util.stream.Collectors;
 import net.minecraft.resources.ResourceKey;
 import org.bukkit.Keyed;
 import org.bukkit.Registry;
+import org.bukkit.craftbukkit.CraftRegistry;
 import org.jetbrains.annotations.VisibleForTesting;
 import org.jspecify.annotations.Nullable;
 
@@ -72,7 +73,7 @@ public class PaperRegistryAccess implements RegistryAccess {
         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);
+        final 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");
         }
@@ -104,13 +105,22 @@ public class PaperRegistryAccess implements RegistryAccess {
         this.registerRegistry(resourceKey, registry, false);
     }
 
+    public <M> void lockReferenceHolders(final ResourceKey<? extends net.minecraft.core.Registry<M>> resourceKey) {
+        final RegistryEntry<M, Keyed> entry = PaperRegistries.getEntry(resourceKey);
+        if (entry == null || !(entry.meta() instanceof final RegistryEntryMeta.ServerSide<M, Keyed> serverSide) || !serverSide.registryTypeMapper().supportsDirectHolders()) {
+            return;
+        }
+        final CraftRegistry<?, M> registry = (CraftRegistry<?, M>) this.getRegistry(entry.apiKey());
+        registry.lockReferenceHolders();
+    }
+
     @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> entry = PaperRegistries.getEntry(resourceKey);
+        final RegistryEntry<M, B> 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());
+        final 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));
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftMusicInstrument.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftMusicInstrument.java
index 520c3e2e1d..7ad11769a1 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftMusicInstrument.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftMusicInstrument.java
@@ -41,9 +41,6 @@ public class CraftMusicInstrument extends MusicInstrument implements io.papermc.
         return io.papermc.paper.util.Holderable.fromBukkitSerializationObject(string, Instrument.CODEC, Registry.INSTRUMENT); // Paper - switch to Holder
     }
 
-    private final NamespacedKey key;
-    private final Instrument handle;
-
     // Paper start - switch to Holder
     @Override
     public boolean equals(final Object o) {
@@ -63,8 +60,6 @@ public class CraftMusicInstrument extends MusicInstrument implements io.papermc.
     private final Holder<Instrument> holder;
     public CraftMusicInstrument(Holder<Instrument> holder) {
         this.holder = holder;
-        this.key = holder.unwrapKey().map(io.papermc.paper.util.MCUtil::fromResourceKey).orElse(null);
-        this.handle = holder.value();
         // Paper end - switch to Holder
     }
 
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
index 2762dd2590..286bc2271f 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
@@ -6,12 +6,15 @@ import io.papermc.paper.registry.set.NamedRegistryKeySetImpl;
 import io.papermc.paper.registry.tag.Tag;
 import io.papermc.paper.util.Holderable;
 import java.util.Collection;
+import io.papermc.paper.util.MCUtil;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.Optional;
 import java.util.function.BiFunction;
 import java.util.stream.Stream;
 import net.minecraft.core.Holder;
+import net.minecraft.core.HolderOwner;
 import net.minecraft.core.RegistryAccess;
 import net.minecraft.resources.ResourceKey;
 import org.bukkit.Keyed;
@@ -162,7 +165,8 @@ public class CraftRegistry<B extends Keyed, M> implements Registry<B> {
     private final net.minecraft.core.Registry<M> minecraftRegistry;
     private final io.papermc.paper.registry.entry.RegistryTypeMapper<M, B> minecraftToBukkit; // Paper - switch to Holder
     private final BiFunction<NamespacedKey, ApiVersion, NamespacedKey> serializationUpdater; // Paper - rename to make it *clear* what it is *only* for
-    private boolean init;
+    private final InvalidHolderOwner invalidHolderOwner = new InvalidHolderOwner();
+    private boolean lockReferenceHolders;
 
     public CraftRegistry(Class<?> bukkitClass, net.minecraft.core.Registry<M> minecraftRegistry, BiFunction<? super NamespacedKey, M, B> minecraftToBukkit, BiFunction<NamespacedKey, ApiVersion, NamespacedKey> serializationUpdater) { // Paper - relax preload class
         // Paper start - switch to Holder
@@ -177,6 +181,22 @@ public class CraftRegistry<B extends Keyed, M> implements Registry<B> {
         this.minecraftRegistry = minecraftRegistry;
         this.minecraftToBukkit = minecraftToBukkit;
         this.serializationUpdater = serializationUpdater;
+        this.lockReferenceHolders = !this.minecraftToBukkit.supportsDirectHolders();
+    }
+
+    public void lockReferenceHolders() {
+        Preconditions.checkState(this.cache.isEmpty(), "Registry %s is already loaded", this.minecraftRegistry.key());
+
+        try {
+            Class.forName(this.bukkitClass.getName()); // this should always trigger the initialization of the class
+        } catch (final ClassNotFoundException e) {
+            throw new IllegalStateException("Failed to load class " + this.bukkitClass.getSimpleName(), e);
+        }
+        if (!this.minecraftToBukkit.supportsDirectHolders()) {
+            return;
+        }
+        Preconditions.checkState(!this.lockReferenceHolders, "Reference holders are already locked");
+        this.lockReferenceHolders = true;
     }
 
     // Paper - inline into CraftRegistry#get(Registry, NamespacedKey, ApiVersion) above
@@ -188,28 +208,19 @@ public class CraftRegistry<B extends Keyed, M> implements Registry<B> {
             return cached;
         }
 
-        // Make sure that the bukkit class is loaded before creating an instance.
-        // This ensures that only one instance with a given key is created.
-        //
-        // Without this code (when bukkit class is not loaded):
-        // Registry#get -> #createBukkit -> (load class -> create default) -> put in cache
-        // Result: Registry#get != <bukkitClass>.<field> for possible one registry item
-        //
-        // With this code (when bukkit class is not loaded):
-        // Registry#get -> (load class -> create default) -> Registry#get -> get from cache
-        // Result: Registry#get == <bukkitClass>.<field>
-        if (!this.init) {
-            this.init = true;
-            try {
-                Class.forName(this.bukkitClass.getName());
-            } catch (ClassNotFoundException e) {
-                throw new RuntimeException("Could not load registry class " + this.bukkitClass, e);
-            }
-
-            return this.get(namespacedKey);
+        final Optional<Holder.Reference<M>> holderOptional = this.minecraftRegistry.get(CraftNamespacedKey.toMinecraft(namespacedKey));
+        final Holder.Reference<M> holder;
+        if (holderOptional.isPresent()) {
+            holder = holderOptional.get();
+        } else if (!this.lockReferenceHolders && this.minecraftToBukkit.supportsDirectHolders()) { // only works if its Holderable
+            // we lock the reference holders after the preload class has been initialized
+            // this is to support the vanilla mechanic of preventing vanilla registry entries being loaded. We need
+            // to create something to fill the API constant fields, so we create a dummy reference holder.
+            holder = Holder.Reference.createStandAlone(this.invalidHolderOwner, MCUtil.toResourceKey(this.minecraftRegistry.key(), namespacedKey));
+        } else {
+            holder = null;
         }
-
-        B bukkit = this.createBukkit(namespacedKey, this.minecraftRegistry.get(CraftNamespacedKey.toMinecraft(namespacedKey)).orElse(null)); // Paper - switch to Holder
+        final B bukkit = this.createBukkit(namespacedKey, holder);
         if (bukkit == null) {
             return null;
         }
@@ -285,4 +296,7 @@ public class CraftRegistry<B extends Keyed, M> implements Registry<B> {
         return this.minecraftRegistry.getTags().<Tag<B>>map(NamedRegistryKeySetImpl::new).toList();
     }
     // Paper end - RegistrySet API
+
+    final class InvalidHolderOwner implements HolderOwner<M> {
+    }
 }
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java b/paper-server/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java
index 34934f0dbe..36825fb8f0 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java
@@ -1,11 +1,11 @@
 package org.bukkit.craftbukkit.enchantments;
 
 import com.google.common.base.Preconditions;
+import io.papermc.paper.util.Holderable;
 import java.util.Locale;
 import net.minecraft.Util;
 import net.minecraft.core.Holder;
 import net.minecraft.core.registries.Registries;
-import net.minecraft.network.chat.contents.TranslatableContents;
 import net.minecraft.tags.EnchantmentTags;
 import org.bukkit.NamespacedKey;
 import org.bukkit.Registry;
@@ -13,13 +13,12 @@ import org.bukkit.craftbukkit.CraftRegistry;
 import org.bukkit.craftbukkit.inventory.CraftItemStack;
 import org.bukkit.craftbukkit.legacy.FieldRename;
 import org.bukkit.craftbukkit.util.ApiVersion;
-import org.bukkit.craftbukkit.util.Handleable;
 import org.bukkit.enchantments.Enchantment;
 import org.bukkit.enchantments.EnchantmentTarget;
 import org.bukkit.enchantments.EnchantmentWrapper;
 import org.bukkit.inventory.ItemStack;
 
-public class CraftEnchantment extends Enchantment implements Handleable<net.minecraft.world.item.enchantment.Enchantment> {
+public class CraftEnchantment extends Enchantment implements Holderable<net.minecraft.world.item.enchantment.Enchantment> {
 
     public static Enchantment minecraftToBukkit(net.minecraft.world.item.enchantment.Enchantment minecraft) {
         return CraftRegistry.minecraftToBukkit(minecraft, Registries.ENCHANTMENT, Registry.ENCHANTMENT);
@@ -56,22 +55,20 @@ public class CraftEnchantment extends Enchantment implements Handleable<net.mine
         return CraftRegistry.get(Registry.ENCHANTMENT, key, ApiVersion.CURRENT);
     }
 
-    private final NamespacedKey key;
     private final Holder<net.minecraft.world.item.enchantment.Enchantment> handle;
 
-    public CraftEnchantment(NamespacedKey key, net.minecraft.world.item.enchantment.Enchantment handle) {
-        this.key = key;
-        this.handle = CraftRegistry.getMinecraftRegistry(Registries.ENCHANTMENT).wrapAsHolder(handle);
+    public CraftEnchantment(Holder<net.minecraft.world.item.enchantment.Enchantment> holder) {
+        this.handle = holder;
     }
 
     @Override
-    public net.minecraft.world.item.enchantment.Enchantment getHandle() {
-        return this.handle.value();
+    public Holder<net.minecraft.world.item.enchantment.Enchantment> getHolder() {
+        return this.handle;
     }
 
     @Override
     public NamespacedKey getKey() {
-        return this.key;
+        return Holderable.super.getKey();
     }
 
     @Override
@@ -251,24 +248,16 @@ public class CraftEnchantment extends Enchantment implements Handleable<net.mine
 
     @Override
     public boolean equals(Object other) {
-        if (this == other) {
-            return true;
-        }
-
-        if (!(other instanceof CraftEnchantment)) {
-            return false;
-        }
-
-        return this.getKey().equals(((Enchantment) other).getKey());
+        return Holderable.super.implEquals(other);
     }
 
     @Override
     public int hashCode() {
-        return this.getKey().hashCode();
+        return Holderable.super.implHashCode();
     }
 
     @Override
     public String toString() {
-        return "CraftEnchantment[" + this.getKey() + "]";
+        return Holderable.super.implToString();
     }
 }
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java
index 9b7488cb45..77378a90fa 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java
@@ -30,9 +30,6 @@ public class CraftTrimMaterial implements TrimMaterial, io.papermc.paper.util.Ho
         return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.TRIM_MATERIAL); // Paper - switch to Holder
     }
 
-    private final NamespacedKey key;
-    private final net.minecraft.world.item.equipment.trim.TrimMaterial handle;
-
     // Paper start - switch to Holder
     private final Holder<net.minecraft.world.item.equipment.trim.TrimMaterial> holder;
 
@@ -64,8 +61,6 @@ public class CraftTrimMaterial implements TrimMaterial, io.papermc.paper.util.Ho
     }
 
     public CraftTrimMaterial(final Holder<net.minecraft.world.item.equipment.trim.TrimMaterial> holder) {
-        this.key = holder.unwrapKey().map(io.papermc.paper.util.MCUtil::fromResourceKey).orElse(null);
-        this.handle = holder.value();
         this.holder = holder;
         // Paper end - switch to Holder
     }
@@ -84,14 +79,14 @@ public class CraftTrimMaterial implements TrimMaterial, io.papermc.paper.util.Ho
     @NotNull
     @Override
     public String getTranslationKey() {
-        if (!(this.handle.description().getContents() instanceof TranslatableContents)) throw new UnsupportedOperationException("Description isn't translatable!"); // Paper
-        return ((TranslatableContents) this.handle.description().getContents()).getKey();
+        if (!(this.getHandle().description().getContents() instanceof TranslatableContents)) throw new UnsupportedOperationException("Description isn't translatable!"); // Paper
+        return ((TranslatableContents) this.getHandle().description().getContents()).getKey();
     }
 
     // Paper start - adventure
     @Override
     public net.kyori.adventure.text.Component description() {
-        return io.papermc.paper.adventure.PaperAdventure.asAdventure(this.handle.description());
+        return io.papermc.paper.adventure.PaperAdventure.asAdventure(this.getHandle().description());
     }
     // Paper end - adventure
 }
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimPattern.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimPattern.java
index b5b209d16a..0d92f77467 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimPattern.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimPattern.java
@@ -30,9 +30,6 @@ public class CraftTrimPattern implements TrimPattern, io.papermc.paper.util.Hold
         return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.TRIM_PATTERN); // Paper - switch to Holder
     }
 
-    private final NamespacedKey key;
-    private final net.minecraft.world.item.equipment.trim.TrimPattern handle;
-
     // Paper start - switch to Holder
     private final Holder<net.minecraft.world.item.equipment.trim.TrimPattern> holder; // Paper - switch to Holder
 
@@ -64,8 +61,6 @@ public class CraftTrimPattern implements TrimPattern, io.papermc.paper.util.Hold
     }
 
     public CraftTrimPattern(Holder<net.minecraft.world.item.equipment.trim.TrimPattern> handle) {
-        this.key = handle.unwrapKey().map(io.papermc.paper.util.MCUtil::fromResourceKey).orElse(null);
-        this.handle = handle.value();
         this.holder = handle;
         // Paper end - switch to Holder
     }
@@ -84,14 +79,14 @@ public class CraftTrimPattern implements TrimPattern, io.papermc.paper.util.Hold
     @NotNull
     @Override
     public String getTranslationKey() {
-        if (!(this.handle.description().getContents() instanceof TranslatableContents)) throw new UnsupportedOperationException("Description isn't translatable!"); // Paper
-        return ((TranslatableContents) this.handle.description().getContents()).getKey();
+        if (!(this.getHandle().description().getContents() instanceof TranslatableContents)) throw new UnsupportedOperationException("Description isn't translatable!"); // Paper
+        return ((TranslatableContents) this.getHandle().description().getContents()).getKey();
     }
 
     // Paper start - adventure
     @Override
     public net.kyori.adventure.text.Component description() {
-        return io.papermc.paper.adventure.PaperAdventure.asAdventure(this.handle.description());
+        return io.papermc.paper.adventure.PaperAdventure.asAdventure(this.getHandle().description());
     }
     // Paper end - adventure
 }
diff --git a/paper-server/src/test/java/org/bukkit/registry/RegistryLoadOrderTest.java b/paper-server/src/test/java/org/bukkit/registry/RegistryLoadOrderTest.java
index 0c704fb9e7..d548ff4a96 100644
--- a/paper-server/src/test/java/org/bukkit/registry/RegistryLoadOrderTest.java
+++ b/paper-server/src/test/java/org/bukkit/registry/RegistryLoadOrderTest.java
@@ -25,7 +25,7 @@ public class RegistryLoadOrderTest {
 
     private static boolean initInterface = false;
     private static boolean initAbstract = false;
-    private static org.bukkit.Registry<Keyed> registry; // Paper - remap fix
+    private static Registry<Keyed> registry;
 
     public static Stream<Arguments> data() {
         return Stream.of(
@@ -60,6 +60,7 @@ public class RegistryLoadOrderTest {
 
         RegistryLoadOrderTest.registry = new CraftRegistry<>(keyedClass, minecraftRegistry, minecraftToBukkit, (namespacedKey, apiVersion) -> namespacedKey);
         this.testClassNotLoaded(init.get());
+        ((CraftRegistry<?, ?>) RegistryLoadOrderTest.registry).lockReferenceHolders();
 
         Object testOne = RegistryLoadOrderTest.registry.get(new NamespacedKey("bukkit", "test-one"));
         Object otherTestOne = RegistryLoadOrderTest.registry.get(new NamespacedKey("bukkit", "test-one"));