javajarminecraftjlinkjdeps

"No jar file system provider found" trying to create a shrink Java image with jdeps and jlink for running Minecraft


I'm trying to create a shrink Java image for running the Minecraft 1.20.1 client using jdeps and jlink (i.e. with only the minimum Java modules required to run Minecraft), however I'm running into errors and I would appreciate some guidance. This is what i did:

I downloaded the libraries listed in the 1.20.1 manifest into my ~/.minecraft/libraries and the client.jar in ~/.minecraft/versions/1.20.1/1.20.1.jar.

To find the Java modules used by the Minecraft client, I ran:

jdeps \
    -q \
    --ignore-missing-deps \
    --print-module-deps \
    --multi-release 17 \
    -cp $HOME'/.minecraft/libraries/*' \
    --recursive \
    $HOME/.minecraft/versions/1.20.1/1.20.1.jar

which gives me:

java.base,java.compiler,java.desktop,java.management,java.naming,java.rmi,java.scripting,java.security.jgss,java.sql,jdk.jfr,jdk.unsupported

Then I passed those modules to jlink as $modules:

jlink --no-header-files --compress=2 --no-man-pages --add-modules $modules --output 'custom-jre'

Finally, I ran the Minecraft client as usual, but now with my newly created shrink JVM

/path/to/custom-jre/bin/java \
    -cp '<whole-list-of-minecraft-jars>' \
    net.minecraft.client.main.Main \
    --version 1.20.1 \
    --gameDir $HOME/.minecraft \
    --accessToken <auth-token>

Crashing with the error message:

Exception in thread "main" java.lang.ExceptionInInitializerError
        at net.minecraft.client.main.Main.main(SourceFile:83)
Caused by: java.lang.IllegalStateException: No jar file system provider found
        at ac.o(SourceFile:101)
        at java.base/java.util.Optional.orElseThrow(Optional.java:403)
        at ac.<clinit>(SourceFile:101)
        ... 1 more

So as you can see, the client started succesfully, but there is an unhandled exception at some point in the Minecraft source code.

Using MCP (a decompiled version of the Minecraft source code) I managed to find the relevant piece of code that was throwing the error:

// src/main/java/net/minecraft/Util.java

public class Util {
   // ....

   public static final FileSystemProvider ZIP_FILE_SYSTEM_PROVIDER = FileSystemProvider.installedProviders().stream().filter((p_201865_) -> {
      return p_201865_.getScheme().equalsIgnoreCase("jar");
   }).findFirst().orElseThrow(() -> {
      return new IllegalStateException("No jar file system provider found");
   });

   // ....
}

I'm not very familiar with Java file systems, but it basically can't find the "jar" file system provider. I wrote this minimal piece of code to illustrate the problem:

import java.nio.file.spi.FileSystemProvider;

public class Test {
    public static void main(String[] args) {
        for (var provider : FileSystemProvider.installedProviders()) {
            System.out.println(provider.getScheme().toString());
        }
    }
}
# Running with the system's JDK 17.0.8 (~259M)
$ java Test
file
jar
jrt

# Running it with my custom JRE: (~58M)
$ /path/to/custom-jre/bin/java Test
file
jrt # <- doesn't have the jar file system

The method I described here has worked with every project I have, it's only Minecraft that causes this issue. If there is a better approach I'd be happy to see it!

So what would be the appropriate solution for this? Am I missing something? Where is that "jar file system" defined? Any help is welcome, thanks in advance :)


Solution

  • Add module jdk.zipfs to your list, that should provide zip Filesystem.

    Also see jdk.zipfs docs and check whether jdeps helps you narrow down any more dependencies.

    UPDATE

    You appear to have used jdeps correctly to analyse module dependencies, but unfortunately it isn't able to help for dynamic code dependency such as based on reflection, services or other class loading.

    You can use jdeps without the summary dependencies --print-module-deps to print a more detailed list such which might give a hint at service providers in packages inside the dependent modules - such as java.nio.file.spi shown here:

    jdeps .....  your.jar
    your.jar -> java.base
       ...
       some.module.name     -> java.nio.file.spi      java.base
    

    You might find the provider name by searching on the package names in the JDK documentation linked above. Then you can use an option for jlink to suggest missing service provider implementation such as java.nio.file.spi.FileSystemProvider:

    jlink --suggest-providers java.nio.file.spi.FileSystemProvider
    
    Suggested providers:
      java.base provides java.nio.file.spi.FileSystemProvider used by java.base
      jdk.zipfs provides java.nio.file.spi.FileSystemProvider used by java.base
    

    Another missing provider which often causes jlink packaging issues is where code needs additional charset support:

    jlink --suggest-providers java.nio.charset.spi.CharsetProvider
    Suggested providers:
       jdk.charsets provides java.nio.charset.spi.CharsetProvider used by java.base