Getting Started with Forge

Why Choose Forge?

Forge offers several advantages for Minecraft modding:

  • Mature ecosystem - Forge has been around for many years with a large community and extensive documentation
  • Powerful API - Comprehensive set of tools for modifying almost every aspect of Minecraft
  • Mod compatibility - Forge mods are designed to work together through a robust event system
  • Stable development - Well-established development practices and patterns
  • Large community - Extensive community support and a wealth of existing mods to learn from

Setting Up Your Development Environment

Setting up for Forge development involves a few key steps:

  1. Install Java Development Kit (JDK) 17 or higher

    Forge for modern Minecraft versions requires JDK 17 or higher. Download it from Adoptium or Oracle.

  2. Choose an IDE

    We recommend IntelliJ IDEA or Eclipse for Java development.

  3. Download the Forge MDK

    Download the Minecraft Development Kit (MDK) for your target Minecraft version from the official Forge website.

  4. Set up the project

    Extract the MDK and run the appropriate setup command for your environment:

    Setup Commands
    12345678910
    # For Windows
    gradlew genEclipseRuns
    gradlew eclipse
    
    # For IntelliJ IDEA
    gradlew genIntellijRuns
    gradlew idea
    
    # For general setup
    gradlew setup

Forge Mod Basics

Mod Structure

A basic Forge mod consists of the following components:

  • mods.toml - The mod metadata file
  • Main mod class - The entry point for your mod
  • Event handlers - Classes that respond to Forge events
  • Resources - Textures, models, and other assets

Here's an example of a mods.toml file:

mods.toml
123456789101112131415161718192021222324252627
# This is an example mods.toml file
modLoader="javafml" 
loaderVersion="[43,)" 
license="MIT"

[[mods]] 
modId="examplemod" 
version="1.0.0" 
displayName="Example Mod" 
authors="Your Name" 
description='''
This is an example mod for Minecraft Forge!
'''

[[dependencies.examplemod]] 
    modId="forge" 
    mandatory=true 
    versionRange="[43,)" 
    ordering="NONE"
    side="BOTH"

[[dependencies.examplemod]]
    modId="minecraft"
    mandatory=true
    versionRange="[1.19.2,1.20)"
    ordering="NONE"
    side="BOTH"

Main Mod Class

The main class is the entry point for your mod. Here's a simple example:

ExampleMod.java
1234567891011121314151617181920212223242526272829303132333435
package com.example.examplemod;

import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

@Mod("examplemod")
public class ExampleMod {
    // Create a logger
    private static final Logger LOGGER = LogManager.getLogger();

    public ExampleMod() {
        // Get the mod event bus
        IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
        
        // Register ourselves for server and other game events
        MinecraftForge.EVENT_BUS.register(this);
        
        // Register the setup method for mod loading
        modEventBus.addListener(this::setup);
        
        // Register item and block registries
        ModItems.register(modEventBus);
        ModBlocks.register(modEventBus);
    }

    private void setup(final FMLCommonSetupEvent event) {
        // Do initial setup
        LOGGER.info("Example mod initialized!");
    }
}

Registering Items and Blocks

Here's how to register items and blocks in Forge:

ModItems.java
12345678910111213141516171819202122
package com.example.examplemod;

import net.minecraft.world.item.Item;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;

public class ModItems {
    // Create a Deferred Register for items
    public static final DeferredRegister<Item> ITEMS = 
        DeferredRegister.create(ForgeRegistries.ITEMS, "examplemod");
    
    // Register a new item
    public static final RegistryObject<Item> EXAMPLE_ITEM = 
        ITEMS.register("example_item", () -> new Item(new Item.Properties()));
    
    // Method to register all items
    public static void register(IEventBus eventBus) {
        ITEMS.register(eventBus);
    }
}

And for blocks:

ModBlocks.java
123456789101112131415161718192021222324252627282930313233343536373839404142
package com.example.examplemod;

import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.material.Material;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;

import java.util.function.Supplier;

public class ModBlocks {
    // Create a Deferred Register for blocks
    public static final DeferredRegister<Block> BLOCKS =
        DeferredRegister.create(ForgeRegistries.BLOCKS, "examplemod");
    
    // Register a new block
    public static final RegistryObject<Block> EXAMPLE_BLOCK = registerBlock("example_block",
        () -> new Block(BlockBehaviour.Properties.of(Material.STONE)
            .strength(2f)
            .requiresCorrectToolForDrops()));
    
    // Helper method to register a block and its item form
    private static <T extends Block> RegistryObject<T> registerBlock(String name, Supplier<T> block) {
        RegistryObject<T> registeredBlock = BLOCKS.register(name, block);
        registerBlockItem(name, registeredBlock);
        return registeredBlock;
    }
    
    // Helper method to register a block item
    private static <T extends Block> void registerBlockItem(String name, RegistryObject<T> block) {
        ModItems.ITEMS.register(name, () -> new BlockItem(block.get(), new Item.Properties()));
    }
    
    // Method to register all blocks
    public static void register(IEventBus eventBus) {
        BLOCKS.register(eventBus);
    }
}

Advanced Forge Concepts

Event System

Forge's event system is a powerful way to hook into Minecraft's code. Here's how to create an event handler:

ModEvents.java
12345678910111213141516171819202122232425
package com.example.examplemod;

import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;

@Mod.EventBusSubscriber(modid = "examplemod")
public class ModEvents {
    
    @SubscribeEvent
    public static void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) {
        Player player = event.getEntity();
        player.sendSystemMessage(Component.literal("Hello, " + player.getName().getString() + "!"));
    }
    
    @SubscribeEvent
    public static void onPlayerBreakSpeed(PlayerEvent.BreakSpeed event) {
        // Increase mining speed by 50% when the player is below Y=0
        if (event.getEntity().getY() < 0) {
            event.setNewSpeed(event.getOriginalSpeed() * 1.5f);
        }
    }
}

Networking in Forge

Forge provides a robust networking system for communication between client and server:

ModMessages.java
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
package com.example.examplemod.networking;

import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.network.NetworkDirection;
import net.minecraftforge.network.NetworkEvent;
import net.minecraftforge.network.NetworkRegistry;
import net.minecraftforge.network.PacketDistributor;
import net.minecraftforge.network.simple.SimpleChannel;

import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;

public class ModMessages {
    private static final String PROTOCOL_VERSION = "1";
    
    // Create a new SimpleChannel for our mod's packets
    public static final SimpleChannel INSTANCE = NetworkRegistry.newSimpleChannel(
        new ResourceLocation("examplemod", "main"),
        () -> PROTOCOL_VERSION,
        PROTOCOL_VERSION::equals,
        PROTOCOL_VERSION::equals
    );
    
    private static int packetId = 0;
    private static int id() {
        return packetId++;
    }
    
    public static void register() {
        // Register packets
        INSTANCE.registerMessage(
            id(),
            ExamplePacket.class,
            ExamplePacket::encode,
            ExamplePacket::decode,
            ExamplePacket::handle
        );
    }
    
    // Send a packet to the server
    public static void sendToServer(Object packet) {
        INSTANCE.sendToServer(packet);
    }
    
    // Send a packet to a specific player
    public static void sendToPlayer(Object packet, ServerPlayer player) {
        INSTANCE.send(PacketDistributor.PLAYER.with(() -> player), packet);
    }
    
    // Example packet class
    public static class ExamplePacket {
        private final String message;
        
        public ExamplePacket(String message) {
            this.message = message;
        }
        
        public static void encode(ExamplePacket packet, FriendlyByteBuf buffer) {
            buffer.writeUtf(packet.message);
        }
        
        public static ExamplePacket decode(FriendlyByteBuf buffer) {
            return new ExamplePacket(buffer.readUtf());
        }
        
        public static void handle(ExamplePacket packet, Supplier<NetworkEvent.Context> contextSupplier) {
            NetworkEvent.Context context = contextSupplier.get();
            context.enqueueWork(() -> {
                // This code runs on the receiving side
                System.out.println("Received message: " + packet.message);
            });
            context.setPacketHandled(true);
        }
    }
}

Config Files

Forge provides a built-in configuration system:

ModConfig.java
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
package com.example.examplemod;

import net.minecraftforge.common.ForgeConfigSpec;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.config.ModConfig;
import org.apache.commons.lang3.tuple.Pair;

public class ModConfig {
    public static class Common {
        public final ForgeConfigSpec.BooleanValue enableFeature;
        public final ForgeConfigSpec.IntValue cooldownTicks;
        public final ForgeConfigSpec.ConfigValue<String> welcomeMessage;
        
        public Common(ForgeConfigSpec.Builder builder) {
            builder.comment("Common configuration settings")
                   .push("common");
            
            enableFeature = builder
                .comment("Whether to enable the example feature")
                .define("enableFeature", true);
            
            cooldownTicks = builder
                .comment("Cooldown in ticks for the example feature")
                .defineInRange("cooldownTicks", 20, 1, 200);
            
            welcomeMessage = builder
                .comment("Welcome message to display to players")
                .define("welcomeMessage", "Hello, world!");
            
            builder.pop();
        }
    }
    
    public static final ForgeConfigSpec COMMON_SPEC;
    public static final Common COMMON;
    
    static {
        final Pair<Common, ForgeConfigSpec> specPair = new ForgeConfigSpec.Builder()
            .configure(Common::new);
        COMMON_SPEC = specPair.getRight();
        COMMON = specPair.getLeft();
    }
    
    public static void register() {
        ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, COMMON_SPEC);
    }
}

Then in your main mod class, register the config:

Config Registration
123456
public ExampleMod() {
    // Register config
    ModConfig.register();
    
    // Rest of your initialization code
}