← Back to Wiki
Self-Hosting / Minecraft

Self-Hosting Minecraft Java With Legacy Forge Mods (1.7.10)

Not every Minecraft mod got ported forward. When the mod you want only exists for a Java Forge build from 2014, running it means a dedicated legacy server. Not an update to whatever you already host. The Bedrock and modern Fabric guides cover the other two flavours this homelab runs side by side. Here is what broke getting a 1.7.10 Forge server running in Docker, and a real rescue mission when a player got stuck somewhere the mod never expected anyone to survive.

Share on X

Why a Bedrock server can't just add the mod

Bedrock and Java Edition are different games under the hood. Different engine, different codebase, no shared mod ecosystem. A mod built for Java Forge cannot run on a Bedrock server, however the Bedrock side is configured. If the mod you want only exists for Java and your existing server is Bedrock, the only real option is a second, dedicated Java server just for it.

Gotcha: the default server image ships a JDK too new for a 2014-era Forge build

itzg/minecraft-server is the well-known Docker image for running Minecraft servers. Use it without pinning a version tag and you get whatever JDK the image currently defaults to, which on a modern pull is far newer than Java 8. Forge's LaunchWrapper for 1.7.10 hard-casts the JVM's system classloader to java.net.URLClassLoader:

java.lang.ClassCastException: class jdk.internal.loader.ClassLoaders$AppClassLoader
cannot be cast to class java.net.URLClassLoader

That cast only works on Java 8. The classloader model changed with the Java 9 module system, and nothing about that is fixable at the Forge-version level. It is a hard incompatibility. Fix: pin the image tag explicitly to java8, not the default or latest tag. Any Forge version below roughly 1.18 needs that tag, whatever the base image currently defaults to. That default has a way of quietly moving forward, so do not assume last year's compose file still pulls Java 8 today.

Minimal working compose file:
services:
  mc:
    image: itzg/minecraft-server:java8
    container_name: mc-java
    ports:
      - "25566:25565"
    environment:
      EULA: "TRUE"
      TYPE: "FORGE"
      VERSION: "1.7.10"
      MEMORY: "4G"
      MOTD: "Homelab Java Server"
    volumes:
      - ./data:/data
    restart: unless-stopped
    stdin_open: true
    tty: true
The image's own auto-installer, TYPE: FORGE with VERSION: 1.7.10, handled the Forge install cleanly on this run. Know this going in. Legacy Forge installers on this image have a documented history of flaking on old vanilla-jar downloads. HTTP 403s from Mojang's legacy asset hosting, install-time NullPointerExceptions. Nothing hit here, but a plausible failure mode for other old Forge versions. Retry before you assume something is fundamentally broken.

Installing an old mod when CurseForge blocks the easy path

CurseForge now requires an API key for their official API, and blocks straightforward scraping of a mod's download page. Hit it directly and you get an HTML page, not the jar file, even with a normal browser user-agent. The CDN's direct file path still works with no authentication, once you know a mod file's numeric file ID, which is visible on the mod's CurseForge page. Split the ID as {fileID // 1000}/{fileID % 1000}:

https://edge.forgecdn.net/files/5737/543/ExampleMod-1.7.10-2.4.0.jar

File ID 5737543 becomes 5737/543. Download that directly, drop it into the mods folder bound to the container's /data/mods, and restart the container. Then confirm in the startup log that the mod loaded. Forge Mod Loader lists every mod it identified on boot. A silently missing mod jar looks identical to a successfully started vanilla server, until someone checks the log or tries to use the mod's content in-game.

Rescuing a player from a mod dimension with no way back

A Forge mod's custom dimension is a one-way trip when the mod's author never built an exit. That happened here for real. A player got dropped into a mod-added pocket dimension by one of the mod's own abilities, with no in-mod portal, item or command to get back. Getting them out took several wrong turns, on a Minecraft version old enough that some assumptions about how commands behave do not hold:

  1. Set gamerule keepInventory true before you force any death. It defaults to false, so a careless kill drops the player's entire inventory somewhere they cannot get back to. Set it to false again immediately after. The second half of this step is easy to forget.
  2. /kill <player> from the console silently fails on 1.7.10. No error, no exception logged. The RCON connection resets before the command executes. 1.7.10's /kill takes no arguments at all. Multiplayer-target support came in a later Minecraft version. Passing an argument a command does not expect broke the RCON round-trip silently rather than erroring cleanly. Remember that as a class of failure, not a one-off.
  3. Having the player run bare /kill themselves, with no argument, failed on a permission error. /kill defaults to operator level 2, and the player was not opped.
  4. Console-side lethal damage via /effect Instant Damage failed too. Silently, twice, even at a very high amplifier. The server's default game mode was Creative, and Creative mode is fully immune to potion-effect damage at any amount. No error. The effect just does nothing to a Creative-mode player.
Here is what worked. Temporarily /op the player. Have them run bare /kill on themselves once opped. Then /deop them immediately. Confirmed via the server log's own death message, "fell out of the world", the vanilla message for the special world-boundary damage source /kill applies internally. That is also why self-/kill succeeds in Creative mode when a normal damage effect never can. It is not routed through the normal damage system at all. Restore keepInventory to its original value immediately after, and confirm the gamerule reads back the value you expect.

The lesson generalizes past this one mod. On an old legacy modded server, do not assume a command that "should" work on a modern server behaves the same way. Check /help <command> for the real argument signature this version supports. And treat a command that silently resets an RCON connection with no logged error as a malformed command for this version. Not a transient connectivity problem worth retrying blindly.