Java is a compiled, object-oriented programming language. Minecraft is written in it. Before diving into mod code, these are the terms you'll see constantly.
Block, Item, Entity are all classes. Your mod files are classes too.new Block(...) creates an instance of the Block class.entity.hurt(...) calls the hurt method on an entity.entity.level is a field holding a reference to the world.implements ModInitializer, it must have onInitialize().class MyBlock extends Block means MyBlock is a Block with extra/overridden behavior.new. Same name as the class, no return type.static final Item MY_ITEM — there's one, shared globally.static final — register once, reference forever.onInitialize() is void — it just runs.EntityType<MyEntity> means "an EntityType specifically for MyEntity."state -> 10 is a tiny function that takes a state and returns 10.com.mymod.blocks keeps your block classes separate from your item classes.Every field, method, and class in Java has a visibility level. This controls what other code can call or read it.
| Modifier | Visible to | Typical use in Minecraft modding |
|---|---|---|
public | Everyone | Registered objects, API methods, your mod's main class |
protected | Same class + subclasses | Methods in Entity/Block subclasses that children override (e.g. registerGoals) |
private | Same class only | Helper methods, internal state. Mixin @Accessor or access wideners can unlock these. |
(none) | Same package | Rare in mod code — usually use public or private explicitly. |
private fields/methods you need to access, you have two options: Access Wideners (unlock at compile time) or Mixin @Accessor / @Invoker (inject access at runtime).| Type | What it holds | Example |
|---|---|---|
int | Whole number | int count = 5; |
float | Decimal, less precise | float speed = 1.5f; |
double | Decimal, more precise | double x = 64.0; |
boolean | true / false | boolean active = true; |
long | Big whole number | long ticks = 72000L; |
| Type | Notes |
|---|---|
String | Text. Use "double quotes". |
List<T> | Ordered collection. List.of(a, b, c) |
Map<K,V> | Key → value lookup table |
Optional<T> | May or may not hold a value. Avoids null crashes. |
Annotations are metadata tags that change how a class or method is compiled or processed. You'll see them everywhere in Fabric mod code.
| Annotation | What it means |
|---|---|
@Override | Replacing a parent class method. Compiler verifies it exists. |
@Nullable | This value might be null. Check before using. |
@Mixin | Mixin-specific: this class patches into a Minecraft class. |
@Inject | Mixin: insert code at a point inside a method. |
@Accessor | Mixin: generate a getter/setter for a private field. |
@Shadow | Mixin: reference an existing field/method in the target class without redefining it. |
@Environment | Fabric: marks code as client-only or server-only. |
Confused about why things go where? Here's the big picture of how Fabric loads and runs your mod.
fabric.mod.jsononInitialize()new ItemStack(...) or access most game objects during class loading (before onInitialize runs). Registration happens inside onInitialize() — not in static initializer blocks on your item class. In 26.1, even inside onInitialize, you can't create ItemStack — use ItemStackTemplate for static item references.// if / else if (entity.isOnFire()) { entity.clearFire(); } else if (entity.isInWater()) { // ... } else { // ... } // for loop for (int i = 0; i < 10; i++) { /* runs 10 times */ } // for-each (iterating a list) for (Item item : myItemList) { System.out.println(item); } // ternary: shorthand if/else that returns a value int light = entity.isOnFire() ? 15 : 0; // ^ condition ^ true ^ false
Fabric events use lambdas heavily. A lambda is just a short way to write a function inline without naming it.
// Full anonymous class (old way — you won't write this) event.register(new MyListener() { public void onTick() { doSomething(); } }); // Lambda (modern — same thing, much shorter) event.register(() -> doSomething()); // Lambda with parameters event.register((server) -> { server.getPlayerList().broadcastMessage(...); }); // Method reference (even shorter — "use this method") event.register(MyMod::doSomething); // Common in block/item registration .lightLevel(state -> state.getValue(LIT) ? 15 : 0) // ^ lambda: takes BlockState, returns int
Minecraft's classes form deep inheritance trees. When you extend a class, you inherit all its methods and can override them. Here are the most important chains:
Entity chainentity.hurt() and you're not sure where it's defined — right-click it in VS Code/IntelliJ → "Go to Definition." You'll land on the method in the parent class that defined it.Two of the most common sources of crashes in Minecraft mod code.
Null / NPE// null = "nothing". Calling a method on null crashes! Player player = entity.getAttackingPlayer(); player.sendMessage(...); // 💥 crash if no attacker! // Always null-check first: if (player != null) { player.sendMessage(...); } // Or use instanceof (also null-safe) if (entity instanceof Player p) { p.sendMessage(...); }Client vs. Server side
// Check which side you're on: if (!level.isClientSide()) { // server-side logic here (spawning, data, loot) } // Client-only code goes in your ClientModInitializer // or behind the !isClientSide() guard on the client.
Every game object (item, block, entity, sound, etc.) is identified by a ResourceLocation — a two-part ID that prevents naming conflicts between mods.
// Format: "namespace:path" // vanilla uses "minecraft:" // your mod uses YOUR mod id ResourceLocation id = ResourceLocation.fromNamespaceAndPath("mymod", "magic_sword"); // result: "mymod:magic_sword" // Parse from a full string: ResourceLocation id2 = ResourceLocation.parse("minecraft:diamond");
fabric.mod.json — always lowercase, no spaces or special chars except underscores. Use it consistently across all resource paths.26.1 is the biggest Fabric toolchain shift ever. Three changes affect every single mod:
modImplementation is gone for Minecraft and Fabric API.# Core versions minecraft_version=26.1.2 loader_version=0.18.4 fabric_version=0.149.1+26.1.2 # Mod metadata mod_version=1.0.0 maven_group=com.yourname archives_base_name=mymod # Java 25 — point Gradle at your JDK 25 install org.gradle.java.home=/path/to/jdk-25 org.gradle.jvmargs=-Xmx2G
net.fabricmc.fabric-loom (not the old fabric-loom). Remove the mappings line entirely.plugins { id 'net.fabricmc.fabric-loom' version "${loom_version}" id 'java' } java { sourceCompatibility = JavaVersion.VERSION_25 targetCompatibility = JavaVersion.VERSION_25 } dependencies { minecraft "com.mojang:minecraft:${minecraft_version}" // No mappings line — game is unobfuscated // Fabric API: use implementation (not modImplementation) implementation "net.fabricmc.fabric-api:fabric-api:${fabric_version}" // Fabric Loader: still uses modImplementation modImplementation "net.fabricmc:fabric-loader:${loader_version}" } // Use 'jar' not 'remapJar' jar { from("LICENSE") { into "META-INF" } }
remapJar → jar · modImplementation → implementation (for Fabric API) · modCompileOnly → compileOnly · modApi → api{
"schemaVersion": 1,
"id": "mymod",
"version": "1.0.0",
"name": "My Mod",
"description": "Does cool stuff",
"environment": "*",
"entrypoints": {
"main": ["com.mymod.MyMod"],
"client": ["com.mymod.MyModClient"]
},
"mixins": ["mymod.mixins.json"],
"depends": {
"fabric-api": ">=0.149.1",
"fabricloader": ">=0.18.4",
"minecraft": ">=26.1.2"
}
}
"fabric-api" as the dependency ID — not "fabric". The fabric mod ID was removed.src/main/resources/ ├── fabric.mod.json ├── mymod.mixins.json ├── assets/mymod/ │ ├── models/item/ ← item models (.json) │ ├── models/block/ ← block models (.json) │ ├── textures/item/ ← item textures (.png) │ ├── textures/block/ │ ├── textures/entity/ │ ├── blockstates/ ← blockstate definitions │ ├── sounds.json │ └── lang/en_us.json ← display names └── data/mymod/ ├── recipe/ ← crafting recipes ├── loot_table/ ← loot tables ├── advancement/ ├── villager_trade/ ← new in 26.1! └── trade_set/ ← new in 26.1! src/main/java/com/mymod/ ├── MyMod.java ← main entrypoint ├── MyModClient.java ← client entrypoint ├── block/ModBlocks.java ├── item/ModItems.java ├── entity/ModEntities.java └── mixin/
// Main entrypoint — runs on BOTH sides public class MyMod implements ModInitializer { public static final String MOD_ID = "mymod"; public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); @Override public void onInitialize() { ModItems.register(); ModBlocks.register(); ModEntities.register(); LOGGER.info("Mymod loaded!"); } } // Client entrypoint — client ONLY (rendering, UI) public class MyModClient implements ClientModInitializer { @Override public void onInitializeClient() { EntityRendererRegistry.register( ModEntities.MY_ENTITY, MyEntityRenderer::new); } }
| Command | What it does |
|---|---|
./gradlew runClient | Launch Minecraft client with your mod loaded |
./gradlew runServer | Launch a local server with your mod |
./gradlew build | Compile mod → build/libs/*.jar |
./gradlew genSourcesWithVineflower | Generate readable Minecraft source to browse in IDE |
./gradlew --refresh-dependencies | Force re-download dependencies (fixes stale caches) |
./gradlew wrapper --gradle-version latest | Update Gradle wrapper |
A record is a class that holds data and nothing else. Java auto-generates constructor, getters, equals, hashCode, and toString. Great for config values, data packets, and snapshots.
// Define a record public record SpawnData(ResourceLocation entityId, int count) {} // Create one SpawnData data = new SpawnData( ResourceLocation.parse("minecraft:zombie"), 3); // Access fields (auto-generated getter = field name) data.entityId(); // → minecraft:zombie data.count(); // → 3 // Records are immutable — no setters, can't change fields // Perfect for custom packet payloads (StreamCodec records)
A supercharged switch that can match on type and destructure objects. Replaces long if/instanceof chains.
// Old way if (entity instanceof Player) { Player p = (Player) entity; p.sendMessage(...); } else if (entity instanceof Zombie) { ... } // New way — cleaner, no manual casts switch (entity) { case Player p -> p.sendMessage(...); case Zombie z -> z.setTarget(null); case LivingEntity l when l.isOnFire() -> doFireStuff(l); default -> {} } // The 'when' clause adds an extra condition to a case
A sealed class/interface declares exactly which classes can extend it. Great for representing a fixed set of states or outcomes that you want to pattern-match over.
// Declare sealed — only listed classes may implement it public sealed interface UseResult permits UseResult.Success, UseResult.Fail, UseResult.Pass { record Success(int cooldown) implements UseResult {} record Fail(String reason) implements UseResult {} record Pass() implements UseResult {} } // Now switch is exhaustive — no default needed switch (result) { case UseResult.Success s -> applyCooldown(s.cooldown()); case UseResult.Fail f -> logFailure(f.reason()); case UseResult.Pass p -> {} }
Multi-line strings with """. Indentation is automatically stripped. Great for inline JSON, data generation, or debug output.
// Triple-quote string — preserves formatting String recipe = """ { "type": "minecraft:crafting_shaped", "pattern": ["XXX", "XYX", "XXX"], "key": { "X": { "item": "minecraft:iron_ingot" }, "Y": { "item": "mymod:magic_gem" } } } """; // Useful for multi-line log messages too LOGGER.info(""" Mod loaded! Version: {} Blocks registered: {} """, mod_version, blockCount);
Virtual threads are lightweight threads managed by the JVM rather than the OS. Useful for async tasks — but never use them for Minecraft tick logic, which must stay on the main thread.
// Platform thread (old, heavy) new Thread(() -> doWork()).start(); // Virtual thread (Java 25, lightweight) Thread.ofVirtual().start(() -> { fetchDataFromExternalAPI(); }); // With a name (useful for debugging) Thread.ofVirtual() .name("mymod-loader") .start(() -> loadConfigAsync());
server.execute(() -> ...) to schedule work back onto the main thread from a virtual thread.// instanceof with capture (Java 16+, fully standard in 25) if (entity instanceof Player p) { // 'p' is already cast — no manual cast needed p.getCooldowns().addCooldown(...); } // var — infers type automatically var pos = entity.blockPosition(); // pos is BlockPos — Java figures it out // String formatting (no more concat mess) String msg = String.format("Entity %s at %s", entity, pos); // or using formatted() shorthand: String msg2 = "Entity %s at %s".formatted(entity, pos);
ItemStack can't be created until a world is loaded. For static references (creative tab icons, etc.) use ItemStackTemplate.public class ModItems { public static final Item MAGIC_SWORD = register("magic_sword", new SwordItem( ToolMaterial.NETHERITE, 7, -2.4f, new Item.Properties())); public static final Item MAGIC_FOOD = register("magic_food", new Item(new Item.Properties() .food(new FoodProperties.Builder() .nutrition(6).saturationModifier(1.2f) .build()))); private static Item register(String name, Item item) { return Registry.register( BuiltInRegistries.ITEM, ResourceLocation.fromNamespaceAndPath(MyMod.MOD_ID, name), item); } public static void register() { MyMod.LOGGER.info("Registering items"); } }
BlockRenderLayerMap calls.public class ModBlocks { public static final Block CRYSTAL_BLOCK = register("crystal_block", new Block(BlockBehaviour.Properties .ofFullCopy(Blocks.AMETHYST_BLOCK) .lightLevel(state -> 10) .strength(3.0f, 6.0f))); private static Block register(String name, Block block) { ResourceLocation id = ResourceLocation.fromNamespaceAndPath(MyMod.MOD_ID, name); // Register BlockItem alongside the block Registry.register(BuiltInRegistries.ITEM, id, new BlockItem(block, new Item.Properties())); return Registry.register( BuiltInRegistries.BLOCK, id, block); } public static void register() {} }
// Add items to an existing tab CreativeModeTabEvents.MODIFY_ENTRIES.register( CreativeModeTabs.COMBAT, (content, canAdd) -> { content.addAfter(Items.NETHERITE_SWORD, ModItems.MAGIC_SWORD); }); // Create a custom tab Registry.register(BuiltInRegistries.CREATIVE_MODE_TAB, ResourceLocation.fromNamespaceAndPath(MyMod.MOD_ID, "main"), CreativeModeTab.builder() .title(Component.translatable("itemGroup.mymod")) .icon(() -> new ItemStackTemplate(ModItems.MAGIC_SWORD)) .displayItems((params, output) -> { output.accept(ModItems.MAGIC_SWORD); output.accept(ModItems.MAGIC_FOOD); }) .build());
ColorProviderRegistry was removed. Use the new BlockColorRegistry.
// OLD (pre-26.1) ColorProviderRegistry.BLOCK.register( (state, level, pos, tint) -> 0x66FF99, ModBlocks.MY_BLOCK); // NEW (26.1) BlockColorRegistry.register( List.of(new BlockTintSource() { @Override public int calculate( BlockState state, BlockAndTintGetter level, BlockPos pos, int tintIndex) { return 0x66FF99; } }), ModBlocks.MY_BLOCK);
// 1 — EntityType in ModEntities.java public class ModEntities { public static final EntityType<MyEntity> MY_ENTITY = Registry.register( BuiltInRegistries.ENTITY_TYPE, ResourceLocation.fromNamespaceAndPath(MyMod.MOD_ID, "my_entity"), EntityType.Builder.<MyEntity> .of(MyEntity::new, MobCategory.MONSTER) .sized(0.8f, 1.8f) .build()); public static void register() { MyMod.LOGGER.info("Registering entities"); } }
// 2 — MyEntity.java public class MyEntity extends PathfinderMob { public MyEntity(EntityType<?> type, Level world) { super(type, world); } @Override protected void registerGoals() { // Priority 1 = highest. Lower number = checked first. this.goalSelector.addGoal(1, new MeleeAttackGoal(this, 1.0, true)); this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 0.8)); this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>( this, Player.class, true)); } // Must be public static — Fabric calls this via reflection public static DefaultAttributeSupplier createAttributes() { return Monster.createMonsterAttributes() .add(Attributes.MAX_HEALTH, 30.0) .add(Attributes.ATTACK_DAMAGE, 5.0) .add(Attributes.MOVEMENT_SPEED, 0.28) .build(); } }
// 3 — Register attributes in onInitialize() FabricDefaultAttributeRegistry.register( ModEntities.MY_ENTITY, MyEntity::createAttributes);
| Goal Class | What it does |
|---|---|
MeleeAttackGoal | Chase and melee-attack target |
RangedAttackGoal | Keep distance and ranged-attack target |
WaterAvoidingRandomStrollGoal | Wander randomly, avoid water |
LookAtPlayerGoal | Face the nearest player |
FloatGoal | Swim when in water |
NearestAttackableTargetGoal | Find a target to attack |
HurtByTargetGoal | Attack whoever hurt you |
FollowOwnerGoal | Tamed mob follows owner |
// In MyModClient.onInitializeClient() EntityRendererRegistry.register( ModEntities.MY_ENTITY, MyEntityRenderer::new); // Minimal renderer extending MobRenderer public class MyEntityRenderer extends MobRenderer<MyEntity, MyEntityModel<MyEntity>> { public MyEntityRenderer(EntityRendererProvider.Context ctx) { super(ctx, new MyEntityModel<>( ctx.bakeLayer(ModEntityModelLayers.MY_ENTITY)), 0.5f); } @Override public ResourceLocation getTextureLocation(MyEntity e) { return ResourceLocation.fromNamespaceAndPath( MyMod.MOD_ID, "textures/entity/my_entity.png"); } }
Mixins let you inject code into Minecraft's own classes without touching their source. Think of it as surgical code grafting — you write a class that patches into a Minecraft class at compile time.
ci.cancel() to cancel a void method. Use CallbackInfoReturnable<T> for methods with return values.compatibilityLevel to "JAVA_25" for 26.1.{
"required": true,
"package": "com.mymod.mixin",
"compatibilityLevel": "JAVA_25",
"mixins": [
"LivingEntityMixin" // both sides
],
"client": [
"GameRendererMixin" // client only
],
"injectors": {
"defaultRequire": 1 // fail build if inject point not found
}
}
@Mixin(LivingEntity.class) public abstract class LivingEntityMixin { // Cancel fall damage @Inject( method = "hurt(Lnet/minecraft/world/damagesource/DamageSource;F)Z", at = @At("HEAD"), cancellable = true) private void onHurt( DamageSource src, float amount, CallbackInfoReturnable<Boolean> cir) { if (src.is(DamageTypes.FALL)) { cir.setReturnValue(false); } } // Run code at method exit (TAIL = before return) @Inject(method = "tick()V", at = @At("TAIL")) private void afterTick(CallbackInfo ci) { // runs at end of every LivingEntity tick } }Common @At targets
| Value | Injects at |
|---|---|
HEAD | Very start of the method |
TAIL | Just before every return |
RETURN | Each return statement |
INVOKE | Before/after a specific method call inside the target |
FIELD | When a field is read or written |
// @Accessor — get/set a private field @Mixin(LivingEntity.class) public interface LivingEntityAccessor { @Accessor("lastHurtMob") LivingEntity getLastHurtMob(); @Accessor("lastHurtMob") void setLastHurtMob(LivingEntity mob); } // Usage — cast to the accessor interface LivingEntity lastHurt = ((LivingEntityAccessor) entity).getLastHurtMob(); // @Invoker — call a private method @Mixin(LivingEntity.class) public interface LivingEntityInvoker { @Invoker("dropAllDeathLoot") void callDropAllDeathLoot(ServerLevel level, DamageSource src); }
.accesswidener file: replace named with official.# OLD (pre-26.1) accessWidener v2 named # NEW (26.1) accessWidener v2 official # Format: accessible/mutable/extendable <type> <class> [member] [descriptor] accessible field net.minecraft.world.entity.LivingEntity lastHurtMob Lnet/minecraft/world/entity/LivingEntity; accessible method net.minecraft.world.level.block.Block updateShape (Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/core/Direction;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/LevelAccessor;Lnet/minecraft/core/BlockPos;Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/level/block/state/BlockState;
Access wideners are a compile-time alternative to @Accessor/@Invoker. They make private members publicly accessible to all your code — not just inside a mixin class.
ItemGroupEvents → CreativeModeTabEvents. See the full rename list at docs.fabricmc.net/develop/porting/fabric-api.| Event | When / what |
|---|---|
ServerLifecycleEvents.SERVER_STARTED | Server fully started and ready |
ServerLifecycleEvents.SERVER_STOPPING | Server is shutting down |
ServerTickEvents.END_SERVER_TICK | End of every server tick (~50ms) |
ClientTickEvents.END_CLIENT_TICK | End of every client tick |
PlayerBlockBreakEvents.BEFORE | Before a block break — cancellable |
UseBlockCallback | Right-clicking a block (general) |
BlockEvents.USE_ITEM_ON | Before Block#useItemOn — non-null return replaces vanilla New in 26.1 |
BlockEvents.USE_WITHOUT_ITEM | Before Block#useWithoutItem New in 26.1 |
ItemEvents.USE_ON | Before Item#useOn New in 26.1 |
ItemEvents.USE | Before Item#use New in 26.1 |
DimensionEvents.MODIFY_ATTRIBUTES | Modify dimension-level atmosphere (clouds, fog, sky) New in 26.1 |
CreativeModeTabEvents.MODIFY_ENTRIES | Add/remove items from creative tabs |
LootTableEvents.MODIFY | Inject loot into existing tables |
EntityElytraEvents.ALLOW | Control elytra flight on entities |
HudElementRegistry | Register HUD elements (replaces removed HudRenderCallback) |
AttackEntityCallback | Player attacking an entity |
ServerEntityEvents.ENTITY_LOAD | Entity loaded into a level |
All Fabric events use the same pattern: find the event, call .register(), pass a lambda.
// In onInitialize() or a dedicated method // Server tick example ServerTickEvents.END_SERVER_TICK.register(server -> { if (server.getTickCount() % 100 == 0) { MyMod.LOGGER.info("100 ticks passed"); } }); // Block break (cancellable) PlayerBlockBreakEvents.BEFORE.register( (level, player, pos, state, entity) -> { if (state.is(Blocks.BEDROCK)) { return false; // cancel the break } return true; }); // Loot injection LootTableEvents.MODIFY.register((key, table, source, lookup) -> { if (key.equals(BuiltInLootTables.SIMPLE_DUNGEON)) { table.pool(LootPool.lootPool() .add(LootItem.lootTableItem(ModItems.MAGIC_SWORD)) .setRolls(ConstantValue.exactly(1))); } });
DimensionEvents.MODIFY_ATTRIBUTES.register( (dimension, attributes, registries) -> { if (dimension.is(BuiltinDimensionTypes.OVERWORLD)) { attributes.set( EnvironmentAttributes.CLOUD_COLOR, 0xFF99FF); // purple clouds! } });
These modifications have lower priority than biome-specific definitions, so they won't override biome mods.
// 1 — Define payload (record = great for this) public record SyncPayload(String data) implements CustomPacketPayload { public static final CustomPacketPayload.Type<SyncPayload> TYPE = new CustomPacketPayload.Type<>( ResourceLocation.fromNamespaceAndPath( MyMod.MOD_ID, "sync")); public static final StreamCodec<FriendlyByteBuf, SyncPayload> CODEC = StreamCodec.composite( ByteBufCodecs.STRING_UTF8, SyncPayload::data, SyncPayload::new); @Override public Type<?> type() { return TYPE; } } // 2 — Register in onInitialize() PayloadTypeRegistry.playS2C() .register(SyncPayload.TYPE, SyncPayload.CODEC); // 3 — Send from server ServerPlayNetworking.send(serverPlayer, new SyncPayload("hello!")); // 4 — Receive on client (in ClientModInitializer) ClientPlayNetworking.registerGlobalReceiver( SyncPayload.TYPE, (payload, ctx) -> { ctx.client().execute(() -> { // ctx.client().execute() = safely back on client thread MyMod.LOGGER.info(payload.data()); }); });
| Class | Notes |
|---|---|
Level | Base world (was World in Yarn) |
ServerLevel | Server-side world — use for spawning, data |
ClientLevel | Client-side world — use for rendering |
MinecraftServer | Server instance |
BlockPos | Immutable int block coordinate |
Vec3 | 3D double vector (precise position) |
ChunkPos | Chunk coordinate (BlockPos / 16) |
| Class | Notes |
|---|---|
Block | Block definition/behavior |
BlockState | Block + all property values at a location |
BlockBehaviour.Properties | Settings builder (strength, light, sound…) |
Item | Item definition |
Item.Properties | Item settings builder |
ItemStack | Item + count + components. Needs world loaded. |
ItemStackTemplate | Safe static item reference before world load New in 26.1 |
DataComponents | Built-in component keys (damage, enchantments…) |
| Class | Notes |
|---|---|
BuiltInRegistries | Static access to ITEM, BLOCK, ENTITY_TYPE, BIOME… |
Registry.register() | Register any game object |
ResourceLocation | Namespaced ID. fromNamespaceAndPath("mod","id") |
ResourceKey | Typed registry key (for datapacks, dimensions) |
| Class | Notes |
|---|---|
Entity | Base for everything in the world |
LivingEntity | Has health, food, equipment slots |
PathfinderMob | Mob with goal-based pathfinding AI |
Monster | Hostile mob base (attacks players by default) |
Animal | Passive mob base (breedable) |
Player | Abstract base player |
ServerPlayer | Server-side player (use for sending packets, data) |
Attributes | MAX_HEALTH, ATTACK_DAMAGE, MOVEMENT_SPEED, ARMOR… |
MobCategory | MONSTER, CREATURE, AMBIENT, WATER_CREATURE, MISC… |
| Class | Notes |
|---|---|
DamageSource | Describes what caused damage (entity, fire, fall…) |
DamageTypes | Built-in damage type registry keys |
| Class | Notes |
|---|---|
Component | Minecraft's rich text object |
Component.literal("...") | Plain text component |
Component.translatable("key") | Localized text component (uses lang file) |
| Class | Notes |
|---|---|
RecipeSerializer | Now wraps MapCodec + StreamCodec directly Changed in 26.1 |
BuiltInRegistries.RECIPE_SERIALIZER | Register custom recipe serializers |
| Class | Notes |
|---|---|
ServerPlayNetworking | Send packets to a client from the server |
ClientPlayNetworking | Send packets to the server; receive from server |
PayloadTypeRegistry | Register your custom packet payload type |
CustomPacketPayload | Interface your packet data class implements |
Several systems moved to datapacks in 26.1. Just create JSON files — no API registration required.
| Feature | Data path |
|---|---|
| Villager trades | data/<ns>/villager_trade/ + add to tag at data/minecraft/tags/villager_trade/<profession> |
| Trade sets | data/<ns>/trade_set/ |
| Recipes | data/<ns>/recipe/ — JSON as before, serializer API simplified |
| Loot tables | data/<ns>/loot_table/ |
| Advancements | data/<ns>/advancement/ |
| Tags | data/<ns>/tags/<registry>/ |
TradeOfferHelper is deprecated in 26.1. Use the villager_trade datapack system instead.ItemStackTemplate for creative tab icons and static fields. new ItemStack(item) crashes before a world is loaded.modImplementation, but Fabric API and mod-to-mod deps use plain implementation.TradeOfferHelper is deprecated. Add JSON to data/<ns>/villager_trade/.HudElementRegistry.accessWidener v2 named to accessWidener v2 official or your build fails.if (x != null) or use instanceof before using a value that might be absent. Most common crash cause.!level.isClientSide() guards.ModBlocks.register() in onInitialize() means it's never added to the registry.Ctrl+.f on float literals — 0.5 is a double. 0.5f is a float. Minecraft uses floats a lot for block/entity sizing.str == "hello" compares identity, not content. Use str.equals("hello").MyMod.LOGGER.info(...) liberally. Logs show in the console during runClient../gradlew genSourcesWithVineflower once after setup. This lets your IDE navigate directly into Minecraft source — invaluable for understanding what methods do and finding correct signatures../gradlew --refresh-dependencies to clear the cache.mymod.mixins.json has the class listed and compatibilityLevel is "JAVA_25".| Resource | URL / command |
|---|---|
| Fabric Docs | docs.fabricmc.net |
| Porting Guide | docs.fabricmc.net/develop/porting |
| API Rename Guide | docs.fabricmc.net/develop/porting/fabric-api |
| Example Mod | fabricmc.net/develop/template/ |
| Fabric Wiki | wiki.fabricmc.net |
| Fabric Discord | discord.fabricmc.net |
| Minecraft Wiki (class search) | minecraft.wiki |
| Generate sources | ./gradlew genSourcesWithVineflower |
| Run client | ./gradlew runClient |
| Build mod jar | ./gradlew build → build/libs/*.jar |