Skip to content

[1.21.1] JPMS ResolutionException when Gradle cache is symlinked to separate mount on Linux #347

Description

@dalizi2333

Environment

  • OS: CachyOS Linux (Arch-based)
  • Java: Temurin 21.0.11 / 21.0.12 (both crash), GraalVM 21.0.2 (crash)
  • NeoForge: 21.1.133 (reproduced with official MDK)
  • ModLauncher: 11.0.4
  • moddev: 2.0.141
  • Gradle: 8.11.1

Description

Running runServer on NeoForge 1.21.1 MDK crashes on Linux with:

Exception in thread "main" java.lang.module.ResolutionException: 
Module fml_loader reads more than one module named fml_earlydisplay
    at MC-BOOTSTRAP/cpw.mods.modlauncher@11.0.4/cpw.mods.modlauncher.ModuleLayerHandler.buildLayer(ModuleLayerHandler.java:75)

Root Cause

The development environment puts earlydisplay-4.0.38.jar and loader-4.0.38.jar in both:

  1. -cp (classpath)
  2. -DlegacyClassPath.file (legacyClasspath)

ModuleLayerHandler.buildLayer collects jars from both sources without deduplication, causing JPMS to detect duplicate modules.

Trigger Condition

Only occurs when ~/.gradle/caches/modules-2/files-2.1 is a symlink to an NTFS partition (e.g., dual-boot setup sharing Gradle cache with Windows). When the symlink is removed and cache lives on btrfs directly, the issue disappears.

Platform Comparison

Environment Gradle cache Result
Windows 11 On NTFS (native) ✅ Works
Linux On NTFS (via symlink) ❌ Crashes
Linux On btrfs (no symlink) ✅ Works
Linux Production (Prism Launcher) ✅ Works

Reproduction

  1. Clone the official MDK: git clone https://github.com/NeoForgeMDKs/MDK-1.21.1-ModDevGradle.git
  2. Symlink ~/.gradle/caches/modules-2/files-2.1 to an NTFS partition
  3. Run ./gradlew runServer --no-daemon
  4. Observe ResolutionException

Impact

  • Linux users with dual-boot setups who share Gradle cache with Windows
  • Development environment only; production (Prism Launcher, server jar) works fine
  • NeoForge 26.1.2 (mc2612) is NOT affected (uses different loader - FancyModLoader)

Suggested Fix

In ModuleLayerHandler.buildLayer(), deduplicate the targets list:

List<String> targets = Arrays.stream(finder)
    .map(SecureJar::name)
    .distinct()
    .toList();

Or, in the moddev Gradle plugin, avoid putting the same jars in both -cp and legacyClasspath.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions