Find the missing module

My question: when building a minimal JRE, how can one make sure that no required module is missing?


To illustrate the question, here is an example where I want to build a minimal JRE for my project. Let's assume for this example that logback is my only dependency.

I run the following command to see what modules are required:

$ jar --file=logback-core-1.2.3.jar --describe-module
No module descriptor found. Derived automatic module.

[email protected] automatic
requires java.base mandated
contains ch.qos.logback.core
contains ch.qos.logback.core.boolex
etc. (there are more "contains ch.qos.logback.XXX" lines)

It looks like I only need the java.base module and I build my minimal JRE accordingly:

jlink --output jre-min --add-modules java.base

However when running the project with the minimal JRE, I encounter issues using logback's email logger (malformed emails over TLS). Through trial and error, I find that jdk.crypto.cryptoki module is also required:

jlink --output jre-min --add-modules java.base,jdk.crypto.cryptoki

Now my project works fine. How could I have avoided the trial & error step?


The JAR you're using there has "no module descriptor" (see first line of output) and thus can't tell you what modules it depends on, so you have to find out yourself. The canonical tool for that is jdeps but it may not be enough.

Static Dependencies

I wrote a jdeps tutorial that gets you started, but the interesting bit is this section. The gist is this command:

jdeps --class-path 'jars/*' -summary -recursive logback-core-1.2.3.jar

Where jars contains all Logback dependencies. (If you don't have those at hand, you can leave --class-path and -recursive out, but then you don't know which modules the dependencies.) Besides a few other things, the output will list the dependencies on JDK modules.

Dynamic Dependencies

jdeps works by analyzing the byte code, which means it will only find dependencies that are statically linked. Consequently, if a JAR uses reflection, the service loader, or other mechanisms to avoid explicitly mentioning the classes it wants to use, jdeps will not notice them.

To find those cases, you can run an app with the java command-line option -XX:DumpLoadedClassList=classes.lst - it will generate a file classes.lst that lists all loaded classes.

Minimal Runtime

Note that the base module java.base uses a lot of services that are provided by other modules, for example locale data by jdk.localedata. That means a minimal runtime (i.e. one where service provider modules are not included) may miss things that an app needs (in the example, maybe locales).

You can list services with java --describe-module java.base (see list of uses ... in output) and then find potentiual providers for each with jlink's --suggest-providers option.

You can include all of possible providers with jlink's --bind-services option, but that immediately abandons the idea of a "minimal" runtime as it will include a lot of modules. If you're going for "minimal", probably better to include them one by one as needed.

Whatever you do, make sure to thoroughly test your app on the custom-made runtime.