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 :)
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