A new version of Minecraft is coming soon with some changes that affect most mod makers. As always, we ask all players to be patient, and give mod developers time to update to this new version. We kindly ask everyone not to pester them. We also recommend all players make backups of their worlds.

Here is a list of several 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.11 (at the time of writing) to develop mods for Minecraft 1.21.9. Players should install the latest stable version of Fabric Loader (currently 0.17.2).

Yarn Mappings

In this update, several mapping name changes were forced by changes in the vanilla class hierarchy. While a full diff may be found here, there is one major change affecting almost all mods: Entity#getWorld was renamed to Entity#getEntityWorld.

World Render Events

The current event suite for rendering in the world has been removed. A suitable replacement is planned asap, but not ready yet. In the meantime, please use mixins to implement what your mod needs.

Resource Loader API v1

A major rework of the resource loader API is present in the 1.21.9 version of Fabric API. This will make current functionality easier to acomplish, as well as opening doors for features like runtime resource generation in the future.

The first part of this rework has just landed with the focus being on ResourceReloader. Historically this API based itself on the IdentifiableResourceReloadListener interface which allowed ResourceReloader to both be identifiable and specify dependencies. However this API had limits, which prevented to run before another ResourceReloader or was difficult to use in multiloader environments. This has been fixed with this new iteration of the API.

From now on, ResourceReloader do not need to implement a Fabric-provided interface, instead they can be registered with an identifier directly:

- ResourceManagerHelper.get(ResourceType.SERVER_DATA).registerReloadListener(new CustomResourceReloader())
+ ResourceLoader.get(ResourceType.SERVER_DATA).registerReloader(Identifier.of("modid", "custom_resource_reloader"), new CustomResourceReloader());

Ordering is now specified akin to events:

ResourceLoader.get(ResourceType.SERVER_DATA).addReloaderOrdering(
    Identifier.of("other", "other_reloader_to_depend"), // Triggers first
    Identifier.of("modid", "custom_resource_reloader") // Triggers second
);

You can also order based on Vanilla reloaders thanks to ResourceReloaderKeys, which provides both per-resource-reloader an identifier, and two global keys: before and after vanilla.

Thanks to 1.21.9 Vanilla changes the way to register reloaders which need registry access has been simplified, instead of using a specialized registration method, now you can get registries and feature flags in ResourceType.SERVER_DATA reloaders via the shared state Store:

class DataReloader implements ResourceReloader {
    @Override
    public CompletableFuture<Void> reload(
        Store store,
        Executor prepareExecutor,
        Synchronizer reloadSynchronizer,
        Executor applyExecutor
    ) {
        RegistryWrapper.WrapperLookup registries = store.getOrThrow(ResourceLoader.RELOADER_REGISTRY_LOOKUP_KEY);
        FeatureSet featureSet = store.getOrThrow(ResourceLoader.RELOADER_FEATURE_SET_KEY);
        // Code
    }
}

This change also allows for ResourceReloaders to communicate data between them.

See #4574 for more details about what else is planned and the current progress.

Enchantments

Transitive access wideners have been added for all of the utility methods in EnchantmentHelper and Enchantment. We hope that this will make it easier for developers to implement custom enchantment effect components. See #4819 for more details.

Serialization

We have added a new module for utilities related to serialization. Currently, this module includes additional methods in ReadView and WriteView, and provides Codecs for some types that base vanilla doesn’t. Please feel free to suggest anything else that may be useful in this area. See #4745 for more details.

Block Conversions

To closer align with vanilla code flow, OxidizableBlocksRegistry now supports registering a CopperBlockSet direclty. Simply call OxidizableBlocksRegistry.registerCopperBlockSet(set) to register. The prior methods for block pairs are still avalible for those that prefer them. See #4807 for more details.

StrippableBlockRegistry now provides multiple overloads to satisfy all your stripping needs. As before, simple conversions can be registered by calling StrippableBlockRegistry.register(Block, Block). You may also call StrippableBlockRegistry.registerCopyState(Block, Block) to register a stripping conversion that automatically copies all properties from the previous state, or register(Block, Block, StrippingTransformer) to have full control over the stripping process. See #4829 for more details.

GUI Rendering

When using a custom RenderPipeline to render in the GUI, vanilla may assume that the VertexFormat being used is QUADS. Fabric now provides a way to override this behavior with RenderPipeline.Builder.withUsePipelineDrawModeForGui.

var pipeline = RenderPipeline.builder(
        snippet1, 
        snippet1
    )
    .withUsePipelineDrawModeForGui(true)
    .build();

// ...
fictionalRenderInGuiInNonGuads(pipeline); // will respect the VertexFormat set in the pipeline

See #4824 for more details.

Mixin & MixinExtras

With version 0.17.0 of Fabric Loader, MixinExtras 5.0.0 and Fabric Mixin 0.16.3+mixin.0.8.7 are now bundled.

  • MixinExtras 5.0.0 brings expressions, a new way to discribe mixins to java code in a syntax that mirrors the target, leading to mixins that are easier to maintain, more expressive, and potentially even more compatible with other mixins. See the release notes and wiki pages for more details.
  • Fabric Mixin 0.16.3 brings many bug fixes, along with scaffolding in mixin for potential widespread performance increases.

Screen Key Events

With many breaking changes from mojang regarding keybindings, we’ve taken the opportunity to improve our events. Most parameters in the event have been consolidated into a context object known as a KeyInput. The afterMouseX style events now also take and return a boolean representing whether the event has been consumed. Returning true from these events will prevent further vanilla handling.

See #4846 and #4620 for more details.

Minecraft Changes

Rendering

Almost all world rendering has been reworked to group objects with similar rendering requirements together. Most places now use OrderedRenderCommandQueue to submit things to be drawn later, when all objects of a similar type have been rendered.

Block Entities

Block entities now use OrderedRenderCommandQueue. The following example shows rendering text on the block using the queue.

public class TestBlockEntityRenderer implements BlockEntityRenderer<TestBlockEntity, BlockEntityRenderState> {
    public TestBlockEntityRenderer(BlockEntityRendererFactory.Context context) {
    }

    @Override
    public BlockEntityRenderState createRenderState() {
        return new BlockEntityRenderState();
    }

    @Override
    public void render(BlockEntityRenderState state, MatrixStack matrices, OrderedRenderCommandQueue queue, CameraRenderState cameraRenderState) {
        queue.submitText(
                matrices,
                0,
                0,
                Text.literal("Hello, world!").asOrderedText(),
                false,
                TextRenderer.TextLayerType.NORMAL,
                state.lightmapCoordinates,
                Colors.WHITE,
                0,
                Colors.BLACK
        );
    }
}
public class TestBlockEntityRenderer implements BlockEntityRenderer<TestBlockEntity> {
    public TestBlockEntityRenderer(BlockEntityRendererFactory.Context context) {
    }

    @Override
    public void render(TestBlockEntity entity, float tickProgress, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay, Vec3d cameraPos) {
        MinecraftClient.getInstance().textRenderer.drawWithOutline(
                Text.literal("Hello, world!").asOrderedText(),
                0,
                0,
                Colors.WHITE,
                Colors.BLACK,
                matrices.peek().getPositionMatrix(),
                vertexConsumers,
                light
        );
    }
}

Particles

Particle rendering has been changed to use the same queue system as above. Many good examples for implementing custom rendering via the queue can be found in the vanilla particle classes.

Entities

Entity rendering has changed to use the same queue as shown above. Changes will be similar.

Keybinding Changes

Keybinding categories have become more structured. You could do the following before:

public class ModKeybindings {
  private static final KeyBinding RANDOM_KEYBIND = new KeyBinding(
    "key.test.random_keybind",
    InputUtil.Type.KEYSYM,
    InputUtil.UNKNOWN_KEY.getCode(),
    "key.category.test.main"
  );

  private static void tickKeybindings(MinecraftClient client) {
    while (RANDOM_KEYBIND.wasPressed()) {
      System.out.println("Random keybind pressed!");
    }
  }

  public static void init() {
    ClientTickEvents.END_CLIENT_TICK.register(ModKeybindings::tickKeybindings);
  }
}

Now you could do:

public class ModKeybindings {
  private static final KeyBinding.Category TEST_CATEGORY = KeyBinding.Category.create(Identifier.of("test", "main"));
  private static final KeyBinding RANDOM_KEYBIND = new KeyBinding(
    "key.test.random_keybind",
    InputUtil.Type.KEYSYM,
    InputUtil.UNKNOWN_KEY.getCode(),
    TEST_CATEGORY
  );
  // ...
}

Each category may only be registered once; Registering a category twice, or two categories with the same id will lead to an exception.

When Fabric API is installed, mod-provided categories will be sorted by alphabetically according to their identifiers, first by namespace, and then by path. All vanilla categories will remain in their natural order.

Debug Text API

It is now possible to register debug HUD entries to be added to the debug (F3) overlay. The debug overlay is now also available outside of a world. Use DebugHudEntries to register entries to be rendered as follows:

DebugHudEntries.register(
  Identifier.of("test", "example"), 
  new DebugHudEntry() {
    @Override
    public void render(
      DebugHudLines lines, 
      @Nullable World world, 
      @Nullable WorldChunk clientChunk, 
      @Nullable WorldChunk chunk
    ) {
      if (world != null) lines.addLine("Example in-world line :)");
      else lines.addLine("Example out-of-world line :(");
    }

    @Override
    public boolean canShow(boolean reducedDebugInfo) {
      // return false if your debug text 
      // is not applicable with reduced debug info
      return true;
    }
  }
);

Misc

MacOS

MinecraftClient.IS_SYSTEM_MAC has been replaced by SystemKeycodes.IS_MAC_OS.