Last week I talked about how I got the mod compiling for version 1.21.4. I was optimistic but, as most developers will know, this is only the first step. The next step in the process would be to get the game to actually run.
Stage 2: Can get to Title Screen
After much effort, I now had a version of the mod that compiled. So it was time for Stage 2: getting the game to actually run. So I hit runClient and…

It failed to get past the mod loading stage. I still had work to do.
Set IDs
Since 1.21, blocks and items need to have their ID set on them in their properties. While this is an easy fix, I did have to refactor many of my block’s and item’s constructors as I had just created the properties there, rather than passing them as a parameter.
To do this, I created several functions to create items of each type in the registries, and set their IDs there. As an example, this is the method for creating Butterfly Scrolls:
/** * Register a butterfly scroll. * @param butterflyIndex The index of the butterfly. * @return A new registry object. */ private DeferredHolder<Item, Item> registerButterflyScroll(int butterflyIndex) { String registryId = ButterflyScrollItem.getRegistryId(butterflyIndex); ResourceKey<Item> key = createResourceKey(registryId); return deferredRegister.register(registryId, () -> new ButterflyScrollItem( new Item.Properties().setId(key), dataComponentRegistry, entityTypeRegistry, this, butterflyIndex)); }
The ID’s type is ResourceKey
, which references both a registry and a ResourceLocation
. To save space I created a helper method to construct these keys:
private ResourceKey<Item> createResourceKey(String registryId) { ResourceLocation location = ResourceLocation.fromNamespaceAndPath(ButterfliesMod.MOD_ID, registryId); return ResourceKey.create(Registries.ITEM, location); }
With this new requirement it did mean that I had to move all of the properties out of the constructors and into the registries, but by keeping them within their own methods I’ve still maintained a bit of encapsulation.
Entity Types
A knock on effect of this was the new method for registering spawn eggs:
/** * Register a butterfly spawn egg. * @param registryId The Registry ID of the item. * @return A new registry object. */ private DeferredHolder<Item, Item> registerSpawnEgg(String registryId, DeferredHolder<EntityType<?>, EntityType<? extends Mob>> entityType) { ResourceKey<Item> key = createResourceKey(registryId); return deferredRegister.register(registryId, () -> new SpawnEggItem(entityType.get(), new Item.Properties().setId(key))); }
Java’s generics aren’t always good at figuring out what parameters work with them, which can make methods like these tricky. In this case we use EntityType<? extends Mob>
which, in theory, should work with any entity that inherits from Mob
. But, even though IronGolem
inherits from Mob
, if we pass in a value of type EntityType<IronGolem>
, this won’t compile anymore.
So I went through the code and switched most places where I use EntityType
to also use ? extends Mob
as a parameter. While this did lead to a couple of “unchecked” casts, these parts of the code were safe enough that I knew what the entity type would be.
With these fixes in place I could hit runClient
and make it to the title screen. Stage 2 was complete.
Next Steps
But, it’s still nowhere near the end. I still haven’t managed to create a world yet. And even if I can create a world, there is still a lot of testing that needs to be completed before release. I was aiming for this version to have as few bugs as possible on release, and so far it seems to have gone smoothly.
The title screen is nice, but I’m not here to look at menus. The next test is determining if a world will actually load…