Skip to main content
Essential Loader supports both ModLauncher 8 (Forge 1.16.5) and ModLauncher 9+ (Forge 1.17+). ModLauncher is Forge’s successor to LaunchWrapper, designed with larger ambitions but significantly more complexity.

Version Support

  • ModLauncher 8: Forge 1.16.5
  • ModLauncher 9: Forge 1.17+ (also supports ModLauncher 10 and 11)

Key Differences

ModLauncher 8 vs 9

- Traditional class loading
- Java 8 compatible
- Simpler (relatively) architecture
- Separate language and regular mods

Setup for ModLauncher 8 & 9

1

Add Essential Repository

Add the Essential Maven repository to your build script:
build.gradle.kts
repositories {
    maven("https://repo.essential.gg/repository/maven-public/")
}
2

Configure Shadow Plugin

ModLauncher requires relocation. Add the Shadow plugin:
build.gradle.kts
plugins {
    id("com.github.johnrengelman.shadow") version "8.1.1"
}
3

Add Essential Loader Dependency

val stage0 by configurations.creating { isCanBeConsumed = false }

dependencies {
    stage0("gg.essential:loader-modlauncher8:1.3.2")
}
4

Configure Jar Task with Relocation

This is the critical step. ModLauncher 9 requires relocation:
build.gradle.kts
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar

val jar by tasks.getting(ShadowJar::class) {
    dependsOn(stage0)
    from(zipTree(stage0.singleFile))
    
    // CRITICAL: Relocate stage0 classes
    // We cannot relocate the whole package because stage1.jar must remain
    // in the same place for discovery
    relocate(
        "gg.essential.loader.stage0.EssentialLoader",
        "com.yourmod.essential.loader.stage0.EssentialLoader"
    )
    relocate(
        "gg.essential.loader.stage0.EssentialTransformationService",
        "com.yourmod.essential.loader.stage0.EssentialTransformationService"
    )
    relocate(
        "gg.essential.loader.stage0.EssentialTransformationServiceBase",
        "com.yourmod.essential.loader.stage0.EssentialTransformationServiceBase"
    )
    relocate(
        "gg.essential.loader.stage0.util",
        "com.yourmod.essential.loader.stage0.util"
    )
    
    // Required for service files to be relocated
    mergeServiceFiles()
    
    manifest {
        attributes(
            "FMLModType" to "LIBRARY"
        )
    }
}
Critical for ModLauncher 9+: You MUST relocate stage0, but you MUST NOT relocate the stage1.jar file (it must remain at gg/essential/loader/stage0/stage1.jar for discovery). The relocation configuration above handles this correctly.

TransformationService Entrypoint

ModLauncher uses TransformationService as its early entrypoint, discovered via Java’s ServiceLoader mechanism.

Service File

Essential Loader provides a service file at:
META-INF/services/cpw.mods.modlauncher.api.ITransformationService
Containing:
gg.essential.loader.stage0.EssentialTransformationService
ModLauncher discovers TransformationServices from jars in the mods folder via this ServiceLoader file, similar to how LaunchWrapper uses manifest entries.

Multi-Stage Architecture

Essential Loader uses a multi-stage architecture for maximum flexibility:
  1. Stage 0: Bundled in your mod (with relocation)
  2. Stage 1: Updatable, forwards to stage 2
  3. Stage 2: Auto-updatable, contains all the heavy lifting
Each stage has its own TransformationService implementation, allowing updates to propagate while maintaining compatibility.

ModLauncher 9 Specific Requirements

Package Relocation is Mandatory

ModLauncher 9+ does not allow two jars to share any packages. Failing to relocate stage0 will cause your mod to be incompatible with other mods that also fail to do so.

Unique TransformationService Names

Because each relocated stage0 must have a unique package, all TransformationServices must:
  • Be able to handle being instantiated multiple times
  • Return a unique name for each instance (generated based on the stage0 package)

Module System Challenges

ModLauncher 9 heavily uses Java 9’s module system, which changes almost everything compared to ModLauncher 8. Public members often cannot be accessed via reflection without using Unsafe to bypass module restrictions.

Upgrading Third-Party Mods (KotlinForForge)

Essential Loader can automatically upgrade outdated third-party mods, particularly KotlinForForge.

The Problem

Despite being labeled a “Mod Loader”, ModLauncher fails to provide a method to load the newer of two versions of the same mod. Users frequently have old versions of KotlinForForge installed, which can cause compatibility issues.

ModLauncher 8 Solution

1

Clone LanguageLoadingProvider

Essential Loader uses Unsafe to clone the LanguageLoadingProvider.
2

Replace with SortedLanguageLoadingProvider

The cloned provider is replaced with SortedLanguageLoadingProvider, which sorts jars by implementation version.
3

Pick Most Recent Version

When multiple jars exist for the same implementation name, only the most recent one is loaded.
This only deals with language mods (like KotlinForForge) because that’s what Essential Loader needs. Regular mods use a different mechanism in Forge.

ModLauncher 9 Solution

ModLauncher 9 introduces additional complexity:
File system order is now HashMap iteration order
→ Effectively random on each run

Implementation Details

  1. SortedJarOrPathList: A custom List implementation that sorts elements by version, replacing the regular list for each ModLauncher layer.
  2. SelfRenamingJarMetadata: On-the-fly renames jar metadata to steal the name of jars with overlapping packages, preventing module conflicts.
The SelfRenamingJarMetadata implementation is elegant enough that it would work even if another mod did the same thing - more compatible than ModLauncher itself!

Upgrading Kotlin Stdlib

KotlinForForge sometimes takes a while to update and may make backwards-incompatible changes. Essential Loader can upgrade the Kotlin stdlib independently.

ModLauncher 8 Approach

Language mods work similar to LaunchWrapper
→ Push Kotlin stdlib into URLClassLoader
→ Do this before KotlinForForge is loaded
✓ Simple and effective

ModLauncher 9 Challenges

ModLauncher 9’s class loader is no longer a simple URLClassLoader, so we can’t just push the stdlib into it. Additionally, Forge’s JiJ implementation (called JarJar) doesn’t work properly for language mods.

The JarJar Problem

Forge added JiJ support (called JarJar) in mid-2022, but:
  • It doesn’t work properly for language mods
  • KotlinForForge can’t use it
  • The issue (MinecraftForge#8878) has been open since 2022 with no fix

Solution: Auto-Generate KotlinForForge

1

Detect Outdated Kotlin

Essential Loader detects when KotlinForForge has an outdated Kotlin version.
2

Generate Replacement Jar

Automatically generates a new KotlinForForge jar based on the existing one but with upgraded Kotlin stdlib.
3

Replace Original

The entire KotlinForForge jar is replaced with the generated version.
4

Version Detection

Since the stdlib is exploded into KotlinForForge with metadata lost:
  • Get coroutines version from a file it includes
  • Load stdlib KotlinVersion class in temporary classloader
  • Make an educated guess at serialization version
While this approach involves some educated guesses, it works well enough in practice.

Mod/Language Load Order (ModLauncher 8)

Modern Forge differentiates between regular Mods and Language Mods. Classes in regular mods take priority over language mods, so even after upgrading KotlinForForge, another mod bundling the Kotlin stdlib can override it with an outdated version.

Solution

Essential Loader intercepts the Lambda used to fetch class bytes and adjusts the priority. This must happen before any classes are loaded, so it “registers” (via Unsafe) an EssentialLaunchPluginService with a method that’s invoked at the right time.

Complete Example

Here’s a complete example for ModLauncher 9:

build.gradle.kts

import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar

plugins {
    id("com.github.johnrengelman.shadow") version "8.1.1"
    id("net.minecraftforge.gradle") version "[6.0,6.2)"
}

repositories {
    maven("https://repo.essential.gg/repository/maven-public/")
    maven("https://maven.minecraftforge.net/")
}

val stage0 by configurations.creating { isCanBeConsumed = false }

dependencies {
    minecraft("net.minecraftforge:forge:1.20.1-47.2.0")
    stage0("gg.essential:loader-modlauncher9:1.3.2")
}

val jar by tasks.getting(ShadowJar::class) {
    destinationDirectory.set(layout.buildDirectory.dir("libs"))
    archiveBaseName.set("yourmod")

    dependsOn(stage0)
    from(zipTree(stage0.singleFile))
    
    // Relocate stage0 (required for ModLauncher 9)
    relocate(
        "gg.essential.loader.stage0.EssentialLoader",
        "com.yourmod.essential.loader.stage0.EssentialLoader"
    )
    relocate(
        "gg.essential.loader.stage0.EssentialTransformationService",
        "com.yourmod.essential.loader.stage0.EssentialTransformationService"
    )
    relocate(
        "gg.essential.loader.stage0.EssentialTransformationServiceBase",
        "com.yourmod.essential.loader.stage0.EssentialTransformationServiceBase"
    )
    relocate(
        "gg.essential.loader.stage0.util",
        "com.yourmod.essential.loader.stage0.util"
    )
    
    mergeServiceFiles()
    
    manifest {
        attributes(
            "FMLModType" to "LIBRARY"
        )
    }
}

Troubleshooting

Package Conflict Errors (ModLauncher 9)

Error: “Package X is in both module A and module B”Solution: Ensure stage0 is properly relocated using the relocation configuration shown above.

TransformationService Not Found

Ensure mergeServiceFiles() is called in your ShadowJar configuration. This is required for the service files to be properly relocated.

KotlinForForge Compatibility

If you see Kotlin version conflicts:
  1. Essential Loader will attempt to auto-upgrade KotlinForForge
  2. Check logs for details on the upgrade process
  3. The upgrade happens automatically on game start

File Name Issues (ModLauncher 9)

If a user downloads KotlinForForge from different websites or downloads it twice (browser adds ” (1)” to the name), Forge will derive different automatic module names, causing conflicts.
Essential Loader’s SelfRenamingJarMetadata automatically handles this case by detecting overlapping packages and stealing the appropriate module name.

Platform Differences Summary

✓ Simple architecture
✓ Easy to work with
✗ Old software stack
✗ Limited to 1.8.9-1.12.2

Best Practices

  1. Always Relocate on ModLauncher 9: This is mandatory, not optional.
  2. Don’t Relocate stage1.jar: The stage1.jar file must remain at its original path for discovery.
  3. Use mergeServiceFiles(): Required for TransformationService discovery after relocation.
  4. Test with KotlinForForge: If your mod or its dependencies use Kotlin, test with various KotlinForForge versions.
  5. Monitor Forge Updates: ModLauncher internals can change between Forge versions. Test your mod with new Forge releases.
ModLauncher platforms require more hacks and workarounds than other platforms. Essential Loader’s stage2 auto-update capability is crucial for quickly fixing issues when Forge internals change.

Build docs developers (and LLMs) love