Minecraft 1.21.2, the “Bundles of Bravery” drop, is expected to release soon. Mojang has recently announced that they will release these “drops” throughout the year. Like with other updates, this drop contains some significant changes affecting mod makers.

As always, 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. We also recommend all players to make a backup of their worlds.

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.

Fabric changes

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

Fabric Loader changes

Fabric Loader 0.16.0 was released some time ago. Most notably, this release updates the bundled MixinExtras to 0.4.0.

In addition, Fabric Loader 0.16.7 added support for Java 23. However, you should continue to use Java 21 for mod development and gameplay.

MixinExtras additions

The following new features have been added:

  • @WrapMethod, which allows wrapping the entire method
  • @Cancellable, which allows cancellation from all injectors.
  • namespace parameter in @Share to share values between Mixins

Loom 1.8

Loom 1.8 adds support for configuration caches, Gradle 8.10, and other changes and fixes.

Configuration cache is an opt-in Gradle performance enhancement feature that will be enabled by default in Gradle 9. Because this may break existing setup, we only recommend using this if you are familiar with Gradle buildscripts. To support this change, multi-project optimization has been removed.

New Fabric API changes

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

  • Convention Tags: Add more tags (TelepathicGrunt, Juuz)
  • Crash Report Info: Print the full stack trace from the dedicated server watchdog (TelepathicGrunt)
  • Entity Events: Add after damage event (TheDeathlyCow)
  • Item API: Modify enchantment and add component map builder extensions (TheDeathlyCow)
  • Item Group API: Add API to control creative inventory screen (modmuss50)
  • Loot API: Loot API v3 (modmuss50)
  • Networking: Add MinecraftClient/Server instances to networking contexts (modmuss50)
  • Networking: Add ClientConfigurationConnectionEvents#START (modmuss50)
  • Networking: Add access to ClientConfigurationNetworkHandler in context (Earthcomputer)
  • Object Builder: Add an API to add additional supported blocks to block entity types (modmuss50)
  • Renderer API: Add ShadeMode (PepperCode1)
  • Renderer API: Quads overloads for joml interfaces (SHsuperCM)
  • Resource Conditions: Allow conditions inside pack overlays (Apollo)
  • Resource Loader: Add API to create reload listeners with a registry lookup (modmuss50)
  • Removed Herobrine (Tiny Potato)
  • Transfer API: Crafter support (modmuss50)
  • Transfer API: Add ItemVariant#withComponentChanges (BasiqueEvangelist)

Breaking changes and deprecations

Note: breaking changes related to vanilla changes are addressed separately below.

One deprecated module was removed: fabric-renderer-registries-v1.

In Resource Condition, the test method now takes RegistryOps.RegistryInfoGetter. Tags can no longer have resource conditions. Similarly, in Resource Loader, ResourceReloadListenerKeys#TAGS was removed.

The following deprecations were made since the last blogpost:

  • Content Registries: VillagerInteractionRegistries#registerCollectable was deprecated. Use the vanilla tag, minecraft:villager_picks_up, instead.
  • Loot API: Loot API v2 was deprecated. Use Loot API v3, which passes registry contexts, instead.
  • Networking:ClientConfigurationConnectionEvents#READY was renamed to COMPLETE. (This was done during the 1.21 update cycle, but after we published the last blogpost.)

Minecraft changes

ServerWorld parameters

Many methods that must be executed on the server side only now require ServerWorld to be passed explicitly. Do not cast just to fix a type error; many events and overridden method are still called on both the client side and the server side.

Instead, wrap all logic that must be run only on the server side with if (world instanceof ServerWorld serverWorld). The serverWorld can be passed to those methods.

For example, Entity#damage now requires ServerWorld, and is therefore only called on the server side. There is an additional method, clientDamage, which is called only on the client side.

Block and item settings

Minecraft 1.21.2 uses registry keys to pre-compute certain block or item settings. This allows referencing them in default item components. For example, rather than computing a default name based on an item’s registry key inside getName method if the minecraft:item_name component is not present, every item now contains the minecraft:item_name component.

Because of this, you must manually set registry keys in the settings of every block and item. Failure to do so will result in crashes such as:

  • NullPointerException: Block id not set
  • NullPointerException: Item id not set

Both settings classes provide a simple method which should be called for every item or block with their respective registry key:

Identifier id = Identifier.of("mymod", "test_item");
RegistryKey<Item> key = RegistryKey.of(RegistryKeys.ITEM, id);

Item.Settings settings = new Item.Settings()
    // If your item is based on a block
    .useBlockPrefixedTranslationKey()
    .registryKey(key);

Registry.register(Registries.ITEM, key, new Item(settings));
Identifier id = Identifier.of("mymod", "test_block");
RegistryKey<Block> key = RegistryKey.of(RegistryKeys.BLOCK, id);

Block.Settings settings = new Block.Settings()
    .registryKey(key);

Registry.register(Registries.BLOCK, key, new Block(settings));

Make sure to call the Item.Settings#useBlockPrefixedTranslationKey method for block items so that they use the block.<namespace>.<path> translation key format.

This change affects the following traits:

  • Item names (minecraft:item_name component)
  • Item models (minecraft:item_model component)
  • Block models
  • Block loot table key

Some traits, such as the cooldown group of items with cooldowns, still dynamically look up a registry key, but this pattern should be avoided as much as possible.

Block#getLootTableKey now returns Optional<RegistryKey>. A block without a corresponding loot table now returns an empty optional instead of the minecraft:empty loot table registry key.

In Fabric API, the previously-deprecated FabricBlockSettings class was removed. Use the vanilla AbstractBlock.Settings class instead.

Furnace fuels

Furnace fuels are now registered through an event, which allows access to new parameters:

- FuelRegistry.INSTANCE.add(ModItems.TEST_ITEM, 50);
+ FuelRegistryEvents.BUILD.register((builder, context) -> {
+     builder.add(ModItems.TEST_ITEM, context.baseSmeltTime() / 4);
+ });

The base smelt time can be used to express a fuel’s smelt time in terms of a ratio. The default base smelt time is 200 ticks, or 10 seconds.

Entities

EntityType received a registry key change similar to the block and item one described above. When building an EntityType, pass a RegistryKey for the entity type. The no-argument version of the EntityType.Builder#build method, injected by FabricEntityTypeBuilder, has been removed. For example:

Identifier id = Identifier.of("mymod", "test_entity_type");
RegistryKey<EntityType<?>> key = RegistryKey.of(RegistryKeys.ENTITY_TYPE, id);

EntityType<TestEntity> entityType = EntityType.Builder.<TestEntity>create(TestEntity::new, SpawnGroup.MISC)
    .build(key);

Registry.register(Registries.ENTITY_TYPE, key, entityType);

Mob conversion (such as zombie villager curing or slimes splitting on death) was overhauled. The exact behavior of conversion is now handled by methods implemented by values of the EntityConversionType enum. When using the MobEntity#convertTo method to convert a mob, an EntityConversionContext is now required. This context is also now passed to Fabric API’s ServerLivingEntityEvents#MOB_CONVERSION event.

When creating an entity, a spawn reason is now required:

- Entity pig = EntityType.PIG.create(overworld);
+ Entity pig = EntityType.PIG.create(overworld, SpawnReason.SPAWN_ITEM_USE);

The prefixes of attributes in EntityAttributes, like GENERIC, have been dropped. This matches a change in the identifiers of attributes:

- public static final RegistryEntry<EntityAttribute> GENERIC_ATTACK_KNOCKBACK = register(
-    "generic.attack_knockback",
-    new ClampedEntityAttribute("attribute.name.generic.attack_knockback", 0, 0, 5)
);
+ public static final RegistryEntry<EntityAttribute> ATTACK_KNOCKBACK = register(
    "attack_knockback",
    new ClampedEntityAttribute("attribute.name.attack_knockback", 0, 0, 5)
);

Data generation

A recipe provider must now override getRecipeGenerator instead of generate. The new method takes the registries and exporter, and returns a new instance of RecipeGenerator that actually generates the recipes.

- public void generate(RecipeExporter exporter) {
-    offerPlanksRecipe2(exporter, SIMPLE_BLOCK, ItemTags.ACACIA_LOGS, 1);
+ protected RecipeGenerator getRecipeGenerator(RegistryWrapper.WrapperLookup registries, RecipeExporter exporter) {
+    return new RecipeGenerator(registries, exporter) {
+        @Override
+        public void generate() {
+            offerPlanksRecipe2(SIMPLE_BLOCK, ItemTags.ACACIA_LOGS, 1);

ActionResult

Previously, action results have been represented in several ways, including the ActionResult, ItemActionResult, and TypedActionResult classes. In Minecraft 1.21.2, these classes have been merged into a single ActionResult class, which provides direct replacements for the previous methods for all three classes:

Old New
ActionResult.success(world.isClient()) ActionResult.SUCCESS
TypedActionResult.pass(stack) ActionResult.PASS
TypedActionResult.fail(stack) ActionResult.FAIL
TypedActionResult.success(stack, world.isClient()) ActionResult.SUCCESS
TypedActionResult.consume(stack) ActionResult.CONSUME

If an action replaces the hand stack with another instance of ItemStack, then it should be marked with the ActionResult#withNewHandStack method. For example, an Item#use implementation that replaces the hand stack might be:

public ActionResult use(World world, PlayerEntity user, Hand hand) {
    ItemStack stack = user.getStackInHand(hand);
    
    if (stack.getCount() > 16) {
        ItemStack newStack = new ItemStack(Items.BLAZE_ROD, 16);
        return ActionResult.SUCCESS.withNewHandStack(newStack);
    }
    
    return ActionResult.PASS;
}

On the other hand, if the ItemStack instance for the hand stack is the same as the one provided to an interaction method such as Item#use, the ActionResult#withNewHandStack method should not be called.

This change affects all places where TypedActionResult and ItemActionResult were previously used in Minecraft’s code. In Fabric API, the UseItemCallback event in the Events Interaction module now returns ActionResult instead of TypedActionResult<ItemStack>.

Item components

Because elytra behavior is now controlled by a new item component, minecraft:glider, FabricElytraItem was removed. Add the component to your elytra item instead.

Block entities

In Minecraft 1.21.1 (released in August), a change was made that required mods to add all supported blocks to block entities. For example, a mod with a new sign block must add the block to BlockEntityType#SIGN:

BlockEntityType.SIGN.addSupportedBlock(ModBlocks.TEAL_SIGN);

In Minecraft 1.21.2, block entity types are no longer constructed using builders. Therefore, FabricBlockEntityType.Builder was removed, while FabricBlockEntityTypeBuilder is no longer deprecated.

Registries

Registry now implements RegistryEntryLookup. This resulted in several name changes. To query a RegistryEntry from RegistryKey, use getOptional or getOrThrow. (Same applies to RegistryEntryList from TagKey.) If you want to query the registry value, use getValueOrThrow. See the table below for all changes.

Old New
getEntry getOptional
entryOf getOrThrow
getOrThrow getValueOrThrow
getOrEmpty getOptionalValue
getEntryList getOptional

Similarly, DynamicRegistryManager#get and RegistryWrapper.WrapperLookup#getWrapperOrThrow has been renamed to getOrThrow. getOptionalWrapper was renamed tp getOptional.

Rendering

Entity rendering

Entity rendering has received a large refactor that decouples the Entity instance from its respective rendering calls. EntityRenderer now has an additional type parameter, S extends EntityRenderState, which represents a ‘render state’, which is a mutable class containing only the parameters of the entity that are used in rendering.

Previously, renderer methods accessed the entity instance directly. However, accessing entity information now happens in three steps.

First, the EntityRenderer#createRenderState method is called to construct an instance of S for the given entity with placeholder values:

public T createRenderState() {
    return new T();
}

Next, the EntityRenderer#updateRenderState method performs the operation of copying information from the entity to its render state:

public void updateRenderState(T entity, S state, float tickDelta) {
    super.updateRenderState(entity, state, tickDelta);

    // Example: entity has an 'is saddled' field
    state.isSaddled = entity.isSaddled();
}

Finally, the EntityRenderer#render method (as well as other renderer methods) accesses information from the entity’s render state:

public void render(S state, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light) {
    super.render(state, matrices, vertexConsumers, light);
    
    if (state.isSaddled) {
        // Render the saddle
    }
}

Shaders

In Fabric Rendering API, CoreShaderRegistrationCallback was removed. Vanilla resource pack now allows loading modded core shaders.

Other changes

WorldRenderer methods for rendering certain vertices (like a box) were transferred to VertexRendering.

Recipes

The recipe system was reworked. To summarize:

  • Recipes are now identified using RegistryKey<? extends Recipe>, not Identifier.
  • Recipe and the recipe ID is now server-side only. It cannot be accessed from the client.
  • Instead, clients receive RecipeDisplayEntry. It contains all information needed to display and run the recipe book. Recipes are identified using an integer (NetworkRecipeId) on the client. A recipe can have multiple display entries.
  • IngredientPlacement controls the placement of ingredients on the crafting table, while RecipeMatcher handles recipe matching.

Ingredient is now internally a list of items, not item stacks.

In Fabric API, CustomIngredientSerializer#getCodec: allowEmpty param removed, empty now always disallowed.

Biome

Water cave carvers (which, before 1.18, were seen in seabeds) were removed, along with GenerationStep.Carver.

In Fabric Biome API, biome modification methods like addCarver and removeCarver no longer takes GenerationStep.Carver argument.

Profiler

The shared profiler is now accessed using Profilers#get.

- world.getProfiler().push("tinyPotato");
+ Profilers.get().push("tinyPotato");

Loot tables

LootTables#EMPTY was removed. The game uses Optional to mark the lack of loot tables.

Some loot context-related logics are now shared with recipes, resulting in name changes. LootContextParameter is now ContextParameter, and LootContextType is now ContextType.

Yarn renames

This section only affects those using the Yarn mapping.

As part of regular Yarn maintainace, the following changes were made. These should be a simple replacement:

  • ModelTransformationMode is transferred to net.minecraft.item.
  • RealmsLoadingWidget is renamed to LoadingWidget and transferred to client.gui.widget.
  • Several Brain and Task-related names: see the pull request for details.
  • registryLookupFuture is renamed to registriesFuture in several locations.
  • Codecs#NONNEGATIVE_INT is renamed to NON_NEGATIVE_INT.
  • QUATERNIONF and VECTOR3F are renamed to QUATERNION_F and VECTOR_3F.
  • Screen#initTabNavigation is renamed to refreshWidgetPositions.
  • ComponentMapImpl is renamed to MergedComponentMap.
  • ContainerComponentModifier#create is renamed to apply.
  • ItemStack#encode is renamed to toNbt.