--- a/net/minecraft/world/item/enchantment/ItemEnchantments.java
+++ b/net/minecraft/world/item/enchantment/ItemEnchantments.java
@@ -26,12 +26,25 @@
 import net.minecraft.world.item.Item;
 import net.minecraft.world.item.TooltipFlag;
 import net.minecraft.world.item.component.TooltipProvider;
+// Paper start
+import it.unimi.dsi.fastutil.objects.Object2IntAVLTreeMap;
+// Paper end
 
 public class ItemEnchantments implements TooltipProvider {
-    public static final ItemEnchantments EMPTY = new ItemEnchantments(new Object2IntOpenHashMap<>(), true);
+    // Paper start
+    private static final java.util.Comparator<Holder<Enchantment>> ENCHANTMENT_ORDER = java.util.Comparator.comparing(Holder::getRegisteredName);
+    public static final ItemEnchantments EMPTY = new ItemEnchantments(new Object2IntAVLTreeMap<>(ENCHANTMENT_ORDER), true);
+    // Paper end
     private static final Codec<Integer> LEVEL_CODEC = Codec.intRange(0, 255);
-    private static final Codec<Object2IntOpenHashMap<Holder<Enchantment>>> LEVELS_CODEC = Codec.unboundedMap(Enchantment.CODEC, LEVEL_CODEC)
-        .xmap(Object2IntOpenHashMap::new, Function.identity());
+    private static final Codec<Object2IntAVLTreeMap<Holder<Enchantment>>> LEVELS_CODEC = Codec.unboundedMap(
+            Enchantment.CODEC, LEVEL_CODEC
+        )// Paper start - sort enchantments
+        .xmap(m -> {
+            final Object2IntAVLTreeMap<Holder<Enchantment>> map = new Object2IntAVLTreeMap<>(ENCHANTMENT_ORDER);
+            map.putAll(m);
+            return map;
+        }, Function.identity());
+    // Paper end - sort enchantments
     private static final Codec<ItemEnchantments> FULL_CODEC = RecordCodecBuilder.create(
         instance -> instance.group(
                     LEVELS_CODEC.fieldOf("levels").forGetter(component -> component.enchantments),
@@ -41,16 +54,16 @@
     );
     public static final Codec<ItemEnchantments> CODEC = Codec.withAlternative(FULL_CODEC, LEVELS_CODEC, map -> new ItemEnchantments(map, true));
     public static final StreamCodec<RegistryFriendlyByteBuf, ItemEnchantments> STREAM_CODEC = StreamCodec.composite(
-        ByteBufCodecs.map(Object2IntOpenHashMap::new, Enchantment.STREAM_CODEC, ByteBufCodecs.VAR_INT),
+        ByteBufCodecs.map((v) -> new Object2IntAVLTreeMap<>(ENCHANTMENT_ORDER), Enchantment.STREAM_CODEC, ByteBufCodecs.VAR_INT),
         component -> component.enchantments,
         ByteBufCodecs.BOOL,
         component -> component.showInTooltip,
         ItemEnchantments::new
     );
-    final Object2IntOpenHashMap<Holder<Enchantment>> enchantments;
+    final Object2IntAVLTreeMap<Holder<Enchantment>> enchantments; // Paper
     public final boolean showInTooltip;
 
-    ItemEnchantments(Object2IntOpenHashMap<Holder<Enchantment>> enchantments, boolean showInTooltip) {
+    ItemEnchantments(Object2IntAVLTreeMap<Holder<Enchantment>> enchantments, boolean showInTooltip) { // Paper
         this.enchantments = enchantments;
         this.showInTooltip = showInTooltip;
 
@@ -141,7 +154,7 @@
     }
 
     public static class Mutable {
-        private final Object2IntOpenHashMap<Holder<Enchantment>> enchantments = new Object2IntOpenHashMap<>();
+        private final Object2IntAVLTreeMap<Holder<Enchantment>> enchantments = new Object2IntAVLTreeMap<>(ENCHANTMENT_ORDER); // Paper
         public boolean showInTooltip;
 
         public Mutable(ItemEnchantments enchantmentsComponent) {
