Minecraft 1.20.5 is to be released in the near future with significant changes affecting mod makers.

But first, we need to make this clear: 1.20.5 is a record-breaking update. This development cycle spans from December 18, 2023 to April of 2024, the longest for any “dot release”. We saw 15 snapshots, the first time it became two digits for a dot release (previous record was 1.20.3 for 8 snapshots). And no surprise: the amount of changes is also unprecedented.

For this reason, we ask all players to be patient, and give mod developers time to update to this new version. We ask everyone kindly not to pester them. 1.20.5 is expected to be the last update of the 1.20 series.

We recommend all players to make a backup of the world. For content mod users, creating a new world is recommended, as most mods do not handle world data upgrades of this magnitude.

Here is a list of all major modder-facing changes in this version. Note that all code references are using Yarn mappings; modders using alternative mappings may need to use different names.

Java 21

Minecraft 1.20.5 now requires Java 21 to run. This means mods can be compiled for Java 21 and use the latest features. This also marks the end of 32-bit support.

To set up a development environment you will need to use Java 21, Loom 1.6, and Gradle 8.6 or higher.

Fabric changes

Developers should use Loom 1.6 (at the time of writing) to develop mods for Minecraft 1.20.5. Players should install the latest stable version of Fabric Loader (currently 0.15.10).

Loom 1.5 & 1.6

There has been two major updates to Loom since the last blogpost: 1.5 and 1.6.

Loom 1.5 includes several performance boosts and small bug fixes. Notably, the mixins can now be remapped using the Tiny Remapper instead of Mixin annotation processor. When enabled, the mixins are remapped in-place and the output jar no longer contains a refmap. This is currently experimental and opt-in.

Loom 1.6 also brought more performance improvements. Decompiler caches were added, meaning that changes to access wideners no longer cause the entire game to be decompiled. There is also an update to how Loom handles existing locks (such as when two Gradle runs occur simultaneously) - it will no longer clear the cache, instead waiting for one process to exit. The error message for locked files is now more detailed, as well.

Please see the Loom 1.5 and Loom 1.6 release notes for all of the changes.

Localization

Fabric has recently moved to using Crowdin to help manage translations for our projects. This makes it much easier to contribute translations across all Fabric projects.

Current projects include Fabric API and installer. If you are able to help translate Fabric into your language, please visit the Crowdin website.

New Fabric API changes

With the help of many contributors, Fabric API has received some new features since the last update blog post:

  • New API: Data Attachments. This allows attaching custom data to block entities, chunks, entites, and worlds. Attached data can be saved or persisted between mob conversions. (Syst3ms)
  • Interaction Events: add client after block break event (kevinthegreat1)
  • GameTest API: add a system property for a custom output directory for gametest structures (ErrorCraft)
  • Entity Events: add mob conversion event (Syst3ms)
  • Fabric Rendering: add AtlasSourceTypeRegistry (PepperCode1)
  • Data Generation: add FabricCodecDataProvider (ErrorCraft)
  • Resource Loader: significant refactors to support pack.mcmeta metadata like filters and overlays. (apple502j)
  • Fabric Rendering: add support for custom ColorResolvers (PepperCode1)
  • Fluids Rendering: expose a function for querying the non-default fluid renderer (jellysquid3)
  • Lifecycle Events: add save events to ServerLifecycleEvents (MrNavaStar)
  • Item API: add enchantments API (Syst3ms)

There is one more addition for advanced developers: you can now declare Fabric API as a dependency using BOM or version catalogs. Check the pull request for more details.

Breaking changes and deprecations

Note: breaking changes related to vanilla changes are discussed below.

The following APIs were removed:

  • fabric-containers-v0 (previously deprecated)
  • fabric-events-lifecycle-v0 (previously deprecated)
  • fabric-mining-level-api-v1
  • ScreenRegistry and ScreenHandlerRegistry

ModifyItemAttributeModifiersCallback from the Item API was removed without replacement due to code changes making it infeasible to port.

In Object Builder API, FabricItemSettings was removed. Use vanilla Item.Settings instead; interface injection provides settings added by Fabric API. Similarly, FabricBlockSettings was deprecated and replaced with vanilla AbstractBlock.Settings. FabricEntityTypeBuilder and FabricBlockEntityTypeBuilder were also deprecated; use vanilla builders instead.

Codec-based Resource Conditions

Unrelated to the 1.20.5 update, a significant change was made to Resource Conditions’ internal workings. This update was developed by Apollo and apple502j. The only breaking change to the JSON syntax is the removal of (block/item/fluid)_tags_populated conditions, but data generation is heavily affected.

The ConditionJsonProvider interface has been removed and been replaced by ResourceCondition. ResourceCondition has a test method, for determining whether the condition should pass, and getType, which returns a ResourceConditionType. withConditions methods in data providers now take ResourceCondition instances. Methods in DefaultResourceConditions that created conditions have been moved to ResourceConditions. A custom ResourceConditionType should be registered with ResourceConditions#register.

In addition, fabric:true condition was added. This condition always passes.

Convention Tag unification

Another significant change was made in the Convention Tags. As part of collaborative efforts with NeoForge, TelepathicGrunt has prepared (and we released) the version 2 of the tags. Version 1 is now deprecated.

As the changes are too long to list here, developers are encouraged to check the pull request.

Minecraft changes

In short, these are the veeery major changes:

  • Item Components. Item stacks no longer use NBT as runtime data storage, instead using predefined “components” to keep values like custom name, enchantments, or damage.
  • Networking. Instead of manually processing PacketByteBuf, packets are now serialized using PacketCodecs. In Fabric Networking API, the previous PacketByteBuf-based APIs were removed; the newer, FabricPacket-based API was rewritten to make use of the vanilla CustomPayload interface.
  • Registries. Likely as a preparation for data-driven blocks and items, serialization/deserialization of most objects now require RegistryWrapper.WrapperLookup instance. This is most noticeable in texts and data generation. In addition, loot tables are now managed by a new type of registry called “reloadable registries”.

Item Components

We skip the general description of the item components system, which you can check in the slicedlime’s video: News in Data Pack Version 33 (24w09a): Item Components!.

Now, we begin the journey into the implementation of item components:

There are five main objects in the item components:

  • DataComponentType, a type of components, serving as keys;
  • Component classes (can be any object), serving as values;
  • ComponentMap, a read-only view of components;
  • ComponentMapImpl, which is a ComponentMap that can be modified, and internally a pair of unmodifiable base ComponentMap and the “overrides” that are modified; and
  • ComponentChanges, a map of component type to the changes for the value (either setting it to a specific value or removing it). This can be used as a diff applied to ItemStack; you apply an instance of ComponentChanges to ItemStack/ComponentMapImpl, so that the overrides part of the map reflects the changes. In other parts of the code, ComponentChanges is just used as ComponentMapImpl minus the base.

So, how does this work?

Each item has the base components. In addition to the base components common to all items, like having empty EnchantmentsComponent, some items provide additional base components. A notable example is DataComponentTypes#DAMAGE for armors, tools, and weapons. A component type that exists in the base has a corresponding default value; here the default damage is 0.

An ItemStack can either 1) add an additional component not present in the base; 2) change the components from the one in the base; or 3) remove a component that exists in the base. The final components of ItemStack, obtainable as ComponentMap from ItemStack#getComponents, reflects all three.

Using Item Components

Item components on item stacks have a similar API surface to a map. Here are some common operations that can be done, using the example of an item’s custom name (note that Text is directly used as the component value):

// Similar to Map#get
@Nullable Text name = stack.get(DataComponentTypes.CUSTOM_NAME);

// Similar to Map#put
// Returns the previous value, if any
@Nullable Text oldName = stack.set(DataComponentTypes.CUSTOM_NAME, Text.literal("Lorem ipsum"));

// Similar to Map#getOrDefault
name = stack.getOrDefault(DataComponentTypes.CUSTOM_NAME, Text.empty());

// Similar to Map#remove
@Nullable Text removedName = stack.remove(DataComponentTypes.CUSTOM_NAME);

// Similar to Map#compute
// In this example, we change the color of the item name
// (Note: stack#getName already checks for custom name, so in reality this does not need apply() call.)
stack.apply(DataComponentTypes.CUSTOM_NAME, stack.getName(), current -> current.copy().formatted(Formatting.RED))

Getting/setting the custom data (NBT). This is useful for datapack makers and server-side modders, because custom item components (see below) need to be synced to the clients, while custom data remains an unparsed NBT.

Unlike the previous example, this uses a component record NbtComponent.

Note: component values are supposed to be immutable - even if you can modify it, don’t. Always copy and set().

NbtCompound nbt = ...;
NbtComponent component = NbtComponent.of(nbt);
// Setting
stack.set(DataComponentTypes.CUSTOM_DATA, component);

// Getting a copy
@Nullable var data = stack.get(DataComponentTypes.CUSTOM_DATA);

if (data != null) {
    NbtCompound value = data.copyNbt();
}

// Using NbtComponent#apply, which copies automatically, to modify the NBT
// Note: you might want to use the static method NbtComponent#set instead,
// which makes this a bit shorter
stack.apply(DataComponentTypes.CUSTOM_DATA, NbtComponent.DEFAULT, comp -> comp.apply(currentNbt -> {
    currentNbt.putInt("key", 0);
}));

// Checking if a certain NBT is a subset of the stack NBT
// (also known as: "non-strict matching")
NbtCompound requiredNbt = ...;
boolean match = stack.getOrDefault(DataComponentTypes.CUSTOM_DATA, NbtComponent.DEFAULT).matches(requiredNbt);
// use createPredicate to make Predicate<ItemStack>

Using ComponentChanges to mass-modify the components of ItemStack:

// stack is a damageable item.
// Repair the item and remove custom name.
// Note that the resulting stack has no components because 0 damage is the default from the base components.
var changes = ComponentChanges.builder().add(DataComponentTypes.DAMAGE, 0).remove(DataComponentTypes.CUSTOM_NAME).build();
stack.applyChanges(changes);

Making an item with base components:

Item item = new Item(new Item.Settings().component(DataComponentTypes.CUSTOM_NAME, Text.literal("Hello")));
// Call #component multiple times for multiple base components.
// Note: calling maxDamage automatically adds DAMAGE component.

Making a custom component. Note that like many registered entries, these must be present in both the client and the server.

public static final DataComponentType<Integer> WEIRDNESS = DataComponentType.builder().codec(Codec.INT).packetCodec(PacketCodecs.VAR_INT).build();
// in the initializer
Registry.register(Registries.DATA_COMPONENT_TYPE, new Identifier("example", "weirdness"), WEIRDNESS);

Some caveats

  • Component values should not be modified directly. Using Record or other immutable object is highly recommended. Always copy, modify, then set.
  • Setting an invalid value for a component does not cause an error immediately, but will lead to a crash during save, and possible data corruption. A common example is NONNEGATIVE_INT being used for an incrementing value; when it overflows and the value is set to negatives, saving it would crash.

BlockEntity interaction with components

BlockEntity stores the components when placing a block item stack with components and when pick-blocking. Components are not currently used to serialize block entities themselves. A block entity that uses components, like custom containers, should override addComponents, readComponents, and removeFromCopiedStackNbt methods:

  • addComponents adds the block entity’s stored data to a component builder.
  • readComponents reads the block entity data from components.
  • removeFromCopiedStackNbt removes NBT keys which are now serialized using components when in item stacks. For example, a chest’s held stacks are stored under Items key when the block entity itself is serialized, and stored under a component when an item stack for the block entity is serialized. To prevent double serialization, this method removes Items from the item stack NBT.

Note that LootableContainerBlockEntity handles component changes themselves, so you do not have to reinvent the wheel if you use them.

allowNbtUpdateAnimations method of FabricItem was renamed to allowComponentsUpdateAnimations. FabricItem.getAttributeModifiers changes? isSuitableFor and getFoodComponents of FabricItemStack removed/replaced with vanilla components.

Recipe API

DefaultCustomIngredients#nbt and support for NBT ingredients were removed, as the game no longer uses NBT to store item stack-specific data.

To check for custom name, enchantments, etc that were previously kept inside the NBT but are now recorded in the components (see below for details), use the new components ingredient. To check for custom data component (a NBT data not used by the game but can be used by data packs or commands), use the customData ingredient.

Transfer API

TransferVariant (such as FluidVariant or ItemVariant) is now a pair of the object and the components, instead of the object and NBT. getNbt, hasNbt, and nbtMatches methods were replaced with getComponents, hasComponents, and componentsMatch methods. The following methods were removed: copyNbt, copyOrCreateNbt, toNbt, and toPacket.

FluidVariant#of and ItemVariant#of now takes ComponentChanges instead of @Nullable NbtCompound.

TransferVariant is now serialized using codecs and packet codecs. Therefore, fromNbt and fromPacket static methods were removed.

SingleVariantStorage#writeNbt instance method was removed; subclasses now provide separate methods named writeNbt. Both readNbt and writeNbt now require passing a RegistryWrapper.WrapperLookup instance; they should be available in the methods from which they are called (or you can use the world’s DynamicRegistryManager instance).

Other Item changes

  • As noted in the video, an empty item stack is now serialized as omitting the field or an empty object. ItemStack#fromNbt now returns Optional<ItemStack> while fromNbtOrEmpty supports empty NBT object. Both now require passing the registries. These methods log an error when an item with ID minecraft:air is encountered.
  • writeNbt method was renamed to encode. The one with NbtCompound argument allows adding to the existing compound, like writeNbt.
  • The methods in ItemStack that reference Nbt are generally renamed to reference Components instead.
  • Item#isNbtSynced was removed, specify custom packet codecs instead.
  • Item#getBreakSound was added.
  • Attack damage and mining speed of MiningToolItem and SwordItem are now specified in item settings via attributeModifiers setting.
  • Item tooltips are now given a “tooltip type”, and TooltipContext is now under Item. ItemTooltipCallback Fabric API event now passes the type as well. Note that hide_tooltip item component prevents the event from being invoked.

Item damages

When damaging an item stack held in the hands or in the armor slots, you now pass the EquipmentSlot instead of a callback:

- stack.damage(1, entity, p -> p.sendToolBreakStatus(Hand.MAIN_HAND));
+ stack.damage(1, entity, EquipmentSlot.MAINHAND);

When damaging an item stack held by non-entities (like shears in dispensers), the stack must be broken by the newly passed callback, not by checking the return value.

- if (stack.damage(1, random, null)) stack.setCount(0);
+ stack.damage(1, random, null, () -> stack.setCount(0));

In Fabric API, CustomDamageHandler callback’s signature was changed from ItemStack stack, int amount, LivingEntity entity, Consumer breakCallback to ItemStack stack, int amount, LivingEntity entity, EquipmentSlot slot, Runnable breakCallback. Additionally, calling breakCallback now ignores the return value and vanilla damage handler (as the item is already broken).

Networking

Previously, networking code was simple: reading from, and writing to, PacketByteBuf. Although this method still works, Mojang has added an abstraction layor: PacketCodec.

PacketCodec works like the DataFixerUpper codecs used in JSON/NBT serialization - although they are not compatible with each other. To keep it simple, PacketCodec is a pair of deserializers and serializers - or, decoders and encoders. This change reduces bugs related to wrong read/write order - which is one of the most difficult bugs to identify.

How to create a PacketCodec

Note: A new subclass of PacketByteBuf, RegistryByteBuf, is used for PLAY-phase networking (the one you’re probably using). For ConfigurationNetworking, use PacketByteBuf instead of RegistryByteBuf. PacketCodecs are usually stored in a public static final field.

Suppose we are serializing this record:

public record VirtualHugs(int count) {
    // Note: replaced PacketByteBuf with RegistryByteBuf
    public VirtualHugs(RegistryByteBuf buf) {
        this(buf.readVarInt());
    }

    // Instance method
    public void write(RegistryByteBuf buf) {
        buf.writeVarInt(count);
    }

    // Static method, buffer-first
    public static void write2(RegistryByteBuf buf, VirtualHugs hugs) {
        buf.writeVarInt(hugs.count())
    }
}
  1. The easy method - adopting existing code
PacketCodec<RegistryByteBuf, VirtualHugs> PACKET_CODEC = PacketCodec.of(VirtualHugs::write, VirtualHugs::new);

// Or, using static method:
PACKET_CODEC = PacketCodec.ofStatic(VirtualHugs::write2, VirtualHugs::new);
  1. The new method - building codecs from other codecs, like in DataFixerUpper
// "tuple" is an ordered, un-keyed set of stuff.
// Up to 3 fields can be serialized with vanilla method.
// The constructor always comes last.
CODEC = PacketCodec.tuple(PacketCodecs.VAR_INT, VirtualHugs::count, VirtualHugs::new);

// Or, since there is only one field in VirtualHugs, this works too.
CODEC = PacketCodecs.VAR_INT.xmap(VirtualHugs::new, VirtualHugs::count);

You might see some nasty casting errors in some cases. Call .cast() on the codec to make it go away; it’s generally safe to do so.

Advanced PacketCodecs examples

Here are some more examples:

// For a singleton (like an empty packet). Encoding something other than INSTANCE will error.
// It does not actually touch the buffer.
var singleton = PacketCodec.unit(INSTANCE);

// Using PacketCodec from classic API
Text text = TextCodecs.PACKET_CODEC.decode(buf);
TextCodecs.PACKET_CODEC.encode(buf, text);

// Codec for Optional<Text>
// Compatible with PacketByteBuf#readOptional
var optText = TextCodecs.PACKET_CODEC.collect(PacketCodecs::optional);

// Codec for List<String>
var strings = PacketCodecs.STRING.collect(PacketCodecs.toList());

// or Set<String>, each max 16 chars
// both work fine, compatible with PacketByteBuf#readCollection
var stringSet = PacketCodecs.string(16).collect(PacketCodecs.toCollection(HashSet::new));
stringSet = PacketCodecs.collection(HashSet::new, PacketCodecs.string(16));

// Map<String, BlockPos>
var positions = PacketCodecs.map(HashMap::new, PacketCodecs.STRING, BlockPos.PACKET_CODEC);

// What? DataFixerUpper codecs?
// Nah, these are just serialized as NBTs.
var advancement = PacketCodecs.codec(Advancement.CODEC);

// Serializing NbtCompound
var nbt = PacketCodecs.NBT_COMPOUND;

// Serializing a registry value (by raw ID)
// Note: these require RegistryByteBuf!
PacketCodec<RegistryByteBuf, Item> item = PacketCodecs.registryValue(RegistryKeys.ITEM);
// Or, RegistryEntry
PacketCodec<RegistryByteBuf, Biome> biome = PacketCodecs.registryEntry(RegistryKeys.BIOME);

// Serializing an Enum
var axis = PacketCodecs.indexed(i -> Direction.Axis.VALUES[i], Direction.Axis::ordinal);

Fabric Networking API

Networking API no longer directly uses PacketByteBuf, except in LoginNetworking. Instead, you need to subclass CustomPayload and register it. Note that CustomPayload is a vanilla interface. While this is similar to the packet object-based networking introduced recently, it is different in some ways. (PacketType and FabricPacket were, therefore, removed.)

Compare the following, pre-snapshot packet:

public record SlapPacket(UUID slapped) implements FabricPacket {
    public static final Identifier ID = new Identifier(...);
    public static final PacketType<SlapPacket> TYPE = PacketType.create(ID, SlapPacket::new);
    
    public SlapPacket(PacketByteBuf buf) {
        this(buf.readUuid());
    }
    
    @Override
    public void write(PacketByteBuf buf) {
        buf.writeUuid(slapped);
    }
}

And a new one:

public record SlapPacket(UUID slapped) implements CustomPayload {
    public static final CustomPayload.Id<SlapPacket> PACKET_ID = new CustomPayload.Id<>(new Identifier(...));
    public static final PacketCodec<RegistryByteBuf, SlapPacket> PACKET_CODEC = Uuids.PACKET_CODEC.xmap(SlapPacket::new, SlapPacket::slapped).cast();
}

There are three differences you will notice:

  • Instead of now-removed FabricPacket, we implement CustomPayload.
  • Each CustomPayload implementation has its own CustomPayload.Id. Unlike PacketType, it lacks reference to the decoder.
  • The latter does not have a write method or PacketByteBuf-taking constructor. Instead, PACKET_CODEC field was added. (Note, you can make your PacketCodec from the constructor and the write method, so don’t just remove those!)

But, you may ask, how do you get the decoder if it’s not in Id? Good question. The answer: you need to register it in ModInitializer on both ends (sender and receiver). In fact, Fabric API will happily crash if you don’t before calling registerGlobalReceiver.

// SlapPacket is sent during gameplay
// from the client to the server (serverbound; C2S)
PayloadTypeRegistry.playC2S().register(SlapPacket.PACKET_ID, SlapPacket.PACKET_CODEC);
// Replace with configurationS2C, playS2C, etc.

Then, you can change the receiver. Instead of long parameters, the event callback now gets only 2 parameters: the payload that was sent, and a context object. (Don’t worry, it won’t eat the RAM.)

- ServerPlayNetworking.registerGlobalReceiver(SlapPacket.TYPE, (packet, player, sender) -> {
-   var world = player.getServerWorld();
+ServerPlayNetworking.registerGlobalReceiver(SlapPacket.PACKET_ID, (payload, context) -> {
+   var world = context.player().getServerWorld();
    var entity = world.getEntity(packet.slapped());
});

Here are the available fields in the context object:

PayloadTypeRegistry Class Context Fields
playC2S ServerPlayNetworking player, responseSender
playS2C ClientPlayNetworking client, player, responseSender
configurationC2S ServerConfigurationNetworking networkHandler, responseSender
configurationS2C ClientConfigurationNetworking responseSender

Note that the field shortcuts might be added later.

Finally, sending the packet also uses CustomPayload. Remember to register the codec first!

ClientPlayNetworking.send(SlapPacket.PACKET_ID, new SlapPacket(...));

// for PacketSender:
sender.sendPacket(new SlapPacket(...));

Note: PacketSender no longer provides APIs that use PacketByteBuf. For login networking where custom payloads are not used yet, use the subinterface LoginPacketSender.

Some other Networking API related changes:

  • FutureListeners was removed.
  • GenericFutureListener-taking methods in PacketSender were removed. Use the one taking PacketCallbacks.
  • ServerPlayNetworking#getServer was removed. handler.player.server should work.

Networking-adjacent changes in Fabric API

Particles API

FabricParticleTypes#complex now requires you to pass both Codec and PacketCodec for serializing the particle type. The string-based parser, ParticleType.Factory, was removed.

Recipe API

CustomIngredientSerializer must now override getPacketCodec. The read and write methods were removed. Use Ingredient#PACKET_CODEC for representing another ingredient (which Fabric API patches up to also support ours).

Screen Handler API

ExtendedScreenHandlerType now uses a payload object instead of PacketByteBuf. In the type constructor, you now need to pass PacketCodec:

public static final ExtendedScreenHandlerType<OvenScreenHandler> OVEN = new ExtendedScreenHandlerType((syncId, inventory, data) -> ..., OvenData.PACKET_CODEC);

Notice that the callback got data instead of buf, as well. Note that the payload object can be of any type, whether it be Identifier or your custom record. (These don’t have to be registered in PayloadTypeRegistry.)

ExtendedScreenHandlerFactory#writeScreenOpeningData was replaced with getScreenOpeningData. It takes the player as the sole argument and returns the data class, in this case, OvenData.

Registries

RegistryWrapper & datagen

Many methods for serialization now require RegistryWrapper.WrapperLookup instance. This is used to query registries, and DynamicRegistryManager instance can be used for this purpose. This change is most prominent in text and data generation-related code. This registry is used to query data-driven values during serialization.

Data provider constructors in Fabric API now take RegistryWrapper.WrapperLookup as a parameter; modders should change their constructors to take the instance and pass to the super constructor. In addition, FabricAdvancementProvider#generateAdvancement now also passes the instance.

public class TestBlockLootTableProvider extends FabricBlockLootTableProvider {
-    private TestBlockLootTableProvider(FabricDataOutput output) {
-        super(output);
+    private TestBlockLootTableProvider(FabricDataOutput output, CompletableFuture<RegistryWrapper.WrapperLookup> registryLookup) {
+        super(output, registryLookup);
    }

Loot Registries

Loot tables (including loot-adjacent stuff, namely predicates and item modifiers) are now managed by a special type of registry, called “reloadable registries”. Unlike ordinary registries, they are reloaded during /reload.

LootDataLookup was removed; ReloadableRegistries.Lookup can be obtained from server.getReloadableRegistries(). From there, item modifiers and predicates can be looked up via getRegistryManager; a shortcut exists for loot tables via getLootTable.

Registry-ification of loot table also means that they are now identified with RegistryKey. Use RegistryKey.of(RegistryKeys.LOOT_TABLE, id) to get the registry key. In Fabric API, VillagerInteractionRegistries#registerGiftLootTable now takes the registry key, while the ID-taking method is deprecated.

Fabric API’s Loot API received breaking changes. For the arguments passed to events, ResourceMaanger and LootManager is gone. In the LOAD_ALL event, the loot manager was replaced with Registry<LootTable>. In all places, Identifier is replaced with RegistryKey as described above. In addition, because mutating registry is impossible now, the loot tables are modified before addition; this means that per-table events don’t get the table registry anymore.

Other changes

A new syncing protocol is in place for DynamicRegistryManager entries. Vanilla data packs provide knownPackInfo fields, which are sent by the server on joined clients. Clients then reply with the subset of the info they understand (i.e. loaded on their side). When a registry entry is loaded from a pack with knownPackInfo, and the client has acknowledged the info for the pack, the server tells the client to refer to its own copy of the data, skipping sending the serialized entry over the network. Most importantly, this is currently not used by mod-provided packs, so the registry entries from mods will be sent even if the client has the same mod.

SimpleRegistry no longer supports changing the registered value post-registration (such as by the set method). For this reason, the RegistryEntryRemovedCallback event and the registeyEntryRemoved shortcut in DynamicRegistryView were removed.

Finally, SculkSensorFrequencyRegistry.register now takes RegistryKey<GameEvent> instead of GameEvent.

Blocks

All AbstractBlock public methods to be overridden were made protected and no longer marked as @Deprecated.

- public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity entity, BlockHitResult hit) {
+ protected ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity entity, BlockHitResult hit) {
}

Enchantments

Enchantment constructor now takes Enchantment.Properties, a record of enchantment properties. EnchantmentTarget enum was replaced with tags that specifies enchantment-supporting items.

Brewing

FabricBrewingRecipeRegistry was replaced with FabricBrewingRecipeRegistryBuilder. The new FabricBrewingRecipeRegistryBuilder.BUILD event can be used to add new recipes to the brewing stand.

Entities

SpawnRestriction.Location enum was replaced with an interface, SpawnLocation. SpawnLocationTypes provides predefined SpawnLocations.

Client

In GUI, setting the initial focus of a screen is now done by overriding setInitialFocus, rather than calling it in init. MatrixStack was replaced with the actual matrix in several rendering code.

Rendering

Some places that previously take rgb was updated to take argb instead. The new DyedColorComponent takes argb. Item color providers also take argb instead of rgb, so make sure to wrap your color with ColorHelper.Argb.fullAlpha() or else your item may appear transparent if it is registered to the translucent render layer. Block color providers also argb now, but passing rgb still works.

Other

DataFixerUpper was updated. This includes several breaking changes, such as MapCodec being used in places defining extra fields. Use RecordCodecBuilder#mapCodec instead of create in this case. Another significant change: optionalFieldOf is now strict by default. This means that a decoding error in the field value is now treated as an error, instead of being silently skipped. The lenientOptionalFieldOf method restores old behavior.

On the Minecraft game code side, Util#getResult was replaced with decode(...).getOrThrow() method. Several methods and fields in Codecs were moved to the DFU itself, such as either, xor, and withAlternative (formerly Codecs#alternatively).