Just build a javafx applicationm, made a fat jar and now I want to generate a custom runtime with the jdk modules that the fat jar needs.
I've created the fat jar which works perfectly from console: ( there is however this warning WARNING: Unsupported JavaFX configuration: classes were loaded from 'unnamed module @4efb10bd' ).
What I tried next is to list the jdk modules needed by this jar:
jdeps --print-module-deps mouseMove-1.0.jar
javafx.base
ch.qos.logback.classic.ViewStatusMessagesServlet -> javax.servlet.http.HttpServletRequest not found
ch.qos.logback.classic.ViewStatusMessagesServlet -> javax.servlet.http.HttpServletResponse not found
ch.qos.logback.classic.helpers.MDCInsertingServletFilter -> javax.servlet.Filter not found
ch.qos.logback.classic.helpers.MDCInsertingServletFilter -> javax.servlet.FilterChain not found
ch.qos.logback.classic.helpers.MDCInsertingServletFilter -> javax.servlet.FilterConfig not found
ch.qos.logback.classic.helpers.MDCInsertingServletFilter -> javax.servlet.ServletException not found
ch.qos.logback.classic.helpers.MDCInsertingServletFilter -> javax.servlet.ServletRequest not found
ch.qos.logback.classic.helpers.MDCInsertingServletFilter -> javax.servlet.ServletResponse not found
ch.qos.logback.classic.helpers.MDCInsertingServletFilter -> javax.servlet.http.HttpServletRequest not found
ch.qos.logback.classic.jul.JULHelper -> java.util.logging.Level not found
ch.qos.logback.classic.jul.JULHelper -> java.util.logging.Logger not found
ch.qos.logback.classic.jul.LevelChangePropagator -> java.util.logging.Level not found
ch.qos.logback.classic.jul.LevelChangePropagator -> java.util.logging.LogManager not found
ch.qos.logback.classic.jul.LevelChangePropagator -> java.util.logging.Logger not found
ch.qos.logback.classic.selector.ContextJNDISelector -> javax.naming.Context not found
ch.qos.logback.classic.selector.ContextJNDISelector -> javax.naming.NamingException not found
ch.qos.logback.classic.selector.servlet.ContextDetachingSCL -> javax.naming.Context not found
ch.qos.logback.classic.selector.servlet.ContextDetachingSCL -> javax.naming.NamingException not found
ch.qos.logback.classic.selector.servlet.ContextDetachingSCL -> javax.servlet.ServletContextEvent not found
ch.qos.logback.classic.selector.servlet.ContextDetachingSCL -> javax.servlet.ServletContextListener not found
ch.qos.logback.classic.selector.servlet.LoggerContextFilter -> javax.servlet.Filter not found
ch.qos.logback.classic.selector.servlet.LoggerContextFilter -> javax.servlet.FilterChain not found
ch.qos.logback.classic.selector.servlet.LoggerContextFilter -> javax.servlet.FilterConfig not found
ch.qos.logback.classic.selector.servlet.LoggerContextFilter -> javax.servlet.ServletException not found
ch.qos.logback.classic.selector.servlet.LoggerContextFilter -> javax.servlet.ServletRequest not found
ch.qos.logback.classic.selector.servlet.LoggerContextFilter -> javax.servlet.ServletResponse not found
ch.qos.logback.classic.servlet.LogbackServletContainerInitializer -> javax.servlet.ServletContainerInitializer not found
ch.qos.logback.classic.servlet.LogbackServletContainerInitializer -> javax.servlet.ServletContext not found
ch.qos.logback.classic.servlet.LogbackServletContainerInitializer -> javax.servlet.ServletException not found
ch.qos.logback.classic.servlet.LogbackServletContextListener -> javax.servlet.ServletContextEvent not found
ch.qos.logback.classic.servlet.LogbackServletContextListener -> javax.servlet.ServletContextListener not found
ch.qos.logback.core.boolex.JaninoEventEvaluatorBase -> org.codehaus.janino.ScriptEvaluator not found
ch.qos.logback.core.joran.conditional.PropertyEvalScriptBuilder -> org.codehaus.commons.compiler.CompileException not found
ch.qos.logback.core.joran.conditional.PropertyEvalScriptBuilder -> org.codehaus.janino.ClassBodyEvaluator not found
ch.qos.logback.core.model.processor.InsertFromJNDIModelHandler -> javax.naming.Context not found
ch.qos.logback.core.model.processor.InsertFromJNDIModelHandler -> javax.naming.NamingException not found
ch.qos.logback.core.net.LoginAuthenticator -> javax.mail.Authenticator not found
ch.qos.logback.core.net.LoginAuthenticator -> javax.mail.PasswordAuthentication not found
ch.qos.logback.core.net.SMTPAppenderBase -> javax.mail.Address not found
ch.qos.logback.core.net.SMTPAppenderBase -> javax.mail.Authenticator not found
ch.qos.logback.core.net.SMTPAppenderBase -> javax.mail.BodyPart not found
ch.qos.logback.core.net.SMTPAppenderBase -> javax.mail.Message not found
ch.qos.logback.core.net.SMTPAppenderBase -> javax.mail.Message$RecipientType not found
ch.qos.logback.core.net.SMTPAppenderBase -> javax.mail.Multipart not found
ch.qos.logback.core.net.SMTPAppenderBase -> javax.mail.Session not found
ch.qos.logback.core.net.SMTPAppenderBase -> javax.mail.Transport not found
ch.qos.logback.core.net.SMTPAppenderBase -> javax.mail.internet.AddressException not found
ch.qos.logback.core.net.SMTPAppenderBase -> javax.mail.internet.InternetAddress not found
ch.qos.logback.core.net.SMTPAppenderBase -> javax.mail.internet.MimeBodyPart not found
ch.qos.logback.core.net.SMTPAppenderBase -> javax.mail.internet.MimeMessage not found
ch.qos.logback.core.net.SMTPAppenderBase -> javax.mail.internet.MimeMultipart not found
ch.qos.logback.core.net.SMTPAppenderBase -> javax.naming.Context not found
ch.qos.logback.core.status.ViewStatusMessagesServletBase -> javax.servlet.ServletException not found
ch.qos.logback.core.status.ViewStatusMessagesServletBase -> javax.servlet.http.HttpServlet not found
ch.qos.logback.core.status.ViewStatusMessagesServletBase -> javax.servlet.http.HttpServletRequest not found
ch.qos.logback.core.status.ViewStatusMessagesServletBase -> javax.servlet.http.HttpServletResponse not found
ch.qos.logback.core.testUtil.MockInitialContext -> javax.naming.InitialContext not found
ch.qos.logback.core.testUtil.MockInitialContext -> javax.naming.NamingException not found
ch.qos.logback.core.testUtil.MockInitialContextFactory -> javax.naming.Context not found
ch.qos.logback.core.testUtil.MockInitialContextFactory -> javax.naming.NamingException not found
ch.qos.logback.core.testUtil.MockInitialContextFactory -> javax.naming.spi.InitialContextFactory not found
ch.qos.logback.core.util.JNDIUtil -> javax.naming.Context not found
ch.qos.logback.core.util.JNDIUtil -> javax.naming.InitialContext not found
ch.qos.logback.core.util.JNDIUtil -> javax.naming.NamingException not found
com.sun.javafx.logging.jfr.JFRInputEvent -> jdk.jfr.Category not found
com.sun.javafx.logging.jfr.JFRInputEvent -> jdk.jfr.Description not found
com.sun.javafx.logging.jfr.JFRInputEvent -> jdk.jfr.Enabled not found
com.sun.javafx.logging.jfr.JFRInputEvent -> jdk.jfr.Event not found
com.sun.javafx.logging.jfr.JFRInputEvent -> jdk.jfr.Label not found
com.sun.javafx.logging.jfr.JFRInputEvent -> jdk.jfr.Name not found
com.sun.javafx.logging.jfr.JFRInputEvent -> jdk.jfr.StackTrace not found
com.sun.javafx.logging.jfr.JFRPulseLogger -> jdk.jfr.FlightRecorder not found
com.sun.javafx.logging.jfr.JFRPulsePhaseEvent -> jdk.jfr.Category not found
com.sun.javafx.logging.jfr.JFRPulsePhaseEvent -> jdk.jfr.Description not found
com.sun.javafx.logging.jfr.JFRPulsePhaseEvent -> jdk.jfr.Enabled not found
com.sun.javafx.logging.jfr.JFRPulsePhaseEvent -> jdk.jfr.Event not found
com.sun.javafx.logging.jfr.JFRPulsePhaseEvent -> jdk.jfr.Label not found
com.sun.javafx.logging.jfr.JFRPulsePhaseEvent -> jdk.jfr.Name not found
com.sun.javafx.logging.jfr.JFRPulsePhaseEvent -> jdk.jfr.StackTrace not found
com.sun.javafx.logging.jfr.PulseId -> jdk.jfr.Description not found
com.sun.javafx.logging.jfr.PulseId -> jdk.jfr.Name not found
com.sun.javafx.logging.jfr.PulseId -> jdk.jfr.Relational not found
com.sun.jna.Native -> java.util.logging.Level not found
com.sun.jna.Native -> java.util.logging.Logger not found
com.sun.jna.Native$1 -> java.util.logging.Level not found
com.sun.jna.Native$1 -> java.util.logging.Logger not found
com.sun.jna.NativeLibrary -> java.util.logging.Level not found
com.sun.jna.NativeLibrary -> java.util.logging.Logger not found
com.sun.jna.Platform -> java.util.logging.Level not found
com.sun.jna.Platform -> java.util.logging.Logger not found
com.sun.jna.Structure -> java.util.logging.Level not found
com.sun.jna.Structure -> java.util.logging.Logger not found
com.sun.jna.internal.Cleaner$1 -> java.util.logging.Level not found
com.sun.jna.internal.Cleaner$1 -> java.util.logging.Logger not found
com.sun.jna.internal.ReflectionUtils -> java.util.logging.Level not found
com.sun.jna.internal.ReflectionUtils -> java.util.logging.Logger not found
com.sun.jna.platform.WindowUtils -> java.util.logging.Logger not found
com.sun.jna.platform.WindowUtils$MacWindowUtils -> java.util.logging.Level not found
com.sun.jna.platform.WindowUtils$MacWindowUtils -> java.util.logging.Logger not found
com.sun.jna.platform.dnd.DragHandler -> java.util.logging.Level not found
com.sun.jna.platform.dnd.DragHandler -> java.util.logging.Logger not found
com.sun.jna.platform.dnd.DropHandler -> java.util.logging.Level not found
com.sun.jna.platform.dnd.DropHandler -> java.util.logging.Logger not found
com.sun.jna.platform.win32.DBT$DEV_BROADCAST_DEVICEINTERFACE -> java.util.logging.Logger not found
com.sun.jna.platform.win32.DdemlUtil$DdeAdapter -> java.util.logging.Level not found
com.sun.jna.platform.win32.DdemlUtil$DdeAdapter -> java.util.logging.Logger not found
com.sun.jna.platform.win32.User32Util$MessageLoopThread -> java.util.logging.Level not found
com.sun.jna.platform.win32.User32Util$MessageLoopThread -> java.util.logging.Logger not found
com.sun.jna.platform.win32.W32FileMonitor -> java.util.logging.Level not found
com.sun.jna.platform.win32.W32FileMonitor -> java.util.logging.Logger not found
com.sun.jna.platform.win32.Win32Exception -> java.util.logging.Level not found
com.sun.jna.platform.win32.Win32Exception -> java.util.logging.Logger not found
com.sun.marlin.MaskMarlinAlphaConsumer -> sun.misc.Unsafe JDK removed internal API
com.sun.marlin.OffHeapArray -> sun.misc.Unsafe JDK removed internal API
com.sun.marlin.OffHeapArray$1 -> sun.misc.Unsafe JDK removed internal API
com.sun.marlin.Renderer -> sun.misc.Unsafe JDK removed internal API
com.sun.marlin.RendererNoAA -> sun.misc.Unsafe JDK removed internal API
impl.org.controlsfx.spreadsheet.CellView -> java.util.logging.Logger not found
javafx.fxml.FXMLLoader -> javax.script.Bindings not found
javafx.fxml.FXMLLoader -> javax.script.ScriptEngine not found
javafx.fxml.FXMLLoader -> javax.script.ScriptEngineFactory not found
javafx.fxml.FXMLLoader -> javax.script.ScriptEngineManager not found
javafx.fxml.FXMLLoader -> javax.script.SimpleBindings not found
javafx.fxml.FXMLLoader$Element -> javax.script.ScriptEngine not found
javafx.fxml.FXMLLoader$ScriptElement -> javax.script.Bindings not found
javafx.fxml.FXMLLoader$ScriptElement -> javax.script.Compilable not found
javafx.fxml.FXMLLoader$ScriptElement -> javax.script.CompiledScript not found
javafx.fxml.FXMLLoader$ScriptElement -> javax.script.ScriptContext not found
javafx.fxml.FXMLLoader$ScriptElement -> javax.script.ScriptEngine not found
javafx.fxml.FXMLLoader$ScriptElement -> javax.script.ScriptEngineFactory not found
javafx.fxml.FXMLLoader$ScriptElement -> javax.script.ScriptEngineManager not found
javafx.fxml.FXMLLoader$ScriptElement -> javax.script.ScriptException not found
javafx.fxml.FXMLLoader$ScriptEventHandler -> javax.script.Bindings not found
javafx.fxml.FXMLLoader$ScriptEventHandler -> javax.script.Compilable not found
javafx.fxml.FXMLLoader$ScriptEventHandler -> javax.script.CompiledScript not found
javafx.fxml.FXMLLoader$ScriptEventHandler -> javax.script.ScriptContext not found
javafx.fxml.FXMLLoader$ScriptEventHandler -> javax.script.ScriptEngine not found
javafx.fxml.FXMLLoader$ScriptEventHandler -> javax.script.ScriptException not found
org.controlsfx.control.cell.MediaImageCell -> javafx.scene.media.Media not found
org.controlsfx.control.cell.MediaImageCell -> javafx.scene.media.MediaPlayer not found
org.controlsfx.control.cell.MediaImageCell -> javafx.scene.media.MediaView not found
org.controlsfx.control.spreadsheet.SpreadsheetView -> java.util.logging.Level not found
org.controlsfx.control.spreadsheet.SpreadsheetView -> java.util.logging.Logger not found
org.controlsfx.control.tableview2.FilteredTableView -> java.util.logging.Level not found
org.controlsfx.control.tableview2.FilteredTableView -> java.util.logging.Logger not found
Now I would expected to get the jdk modules like java.base, java.desktop, etc but instead I got this.
My idea is to print the list of all the needed jdk modules that I can then use in jlink to generate a custom runtime.
Then, I can just run my application with the usual $MY_CUSTOM_JVM/bin/java -jar mouseMove-1.0.jar
My question are:
Thanks
Updated 10/06/2023: added the project pom.xml
<?xml version="1.0" encoding="UTF-8"?>
4.0.0
<groupId>com.mouse.move</groupId>
<artifactId>mouseMove</artifactId>
<version>1.0</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<javafx.version>21</javafx.version>
<jna.version>5.13.0</jna.version>
</properties>
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-base</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-graphics</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.3.5</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.4</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.3.5</version>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>${jna.version}</version>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId>
<version>${jna.version}</version>
</dependency>
<dependency>
<groupId>org.controlsfx</groupId>
<artifactId>controlsfx</artifactId>
<version>11.1.2</version>
</dependency>
</dependencies>
<build>
<finalName>${artifactId}-${version}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>21</source>
<target>21</target>
<compilerArgs>--enable-preview</compilerArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.4.1</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>com.mouse.move.MouseMoveMain</mainClass>
</manifest>
</archive>
<appendAssemblyId>false</appendAssemblyId>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Also, the module info:
module mouse.move {
requires javafx.graphics;
requires javafx.controls;
requires javafx.fxml;
requires org.controlsfx.controls;
requires com.sun.jna.platform;
requires org.slf4j;
requires com.sun.jna;
opens com.mouse.move;
}
Now that I am thinking, the right question would be: can you mix a standard java maven program with modules? That is extract from your written classes + all the maven dependencies the jdk modules used so that you can then build a custom jvm image?
How to properly get the jdk modules for a fat JavaFX jar using jdeps
Use the --print-module-deps
option with jdeps
. If the jar being analyzed also includes dependencies on optional code that is not required for your application (as is the case for you), also use --ignore-missing-deps
on the resultant jar file.
For example for your dependencies:
jdeps --print-module-deps --ignore-missing-deps target/mouseMove-1.0.jar
I get:
java.base,java.desktop
So if you run your jar from a jlink image build to include the above modules it should work. But of course, it does not. The reason for this is that you have ignored too many missing depenendencies. Specifically those needed for JavaFX. You can see this because lots of javafx classes show up when you try running without --ignore-mssing-deps
and those are actually really needed.
So let's do this again with the javafx modules provided to jdeps for analysis, using javafx modules downloaded as part of the javafx SDK from gluon:
jdeps --module-path ~/Downloads/javafx-sdk-21/lib --add-modules javafx.graphics,javafx.controls,javafx.fxml --print-module-deps --ignore-missing-deps target/mouseMove-1.0.jar
Now we get:
java.base,java.desktop,java.scripting,jdk.unsupported
Build a new image:
jlink --add-modules java.base,java.desktop,java.scripting,jdk.unsupported --output customjre
Run it:
customjre/bin/java -jar target/mouseMove-1.0.jar
It works.
In order for this to work, the unsupported hack of creating an additional class not extending Application that invokes launch on the class extending Application was used, as documented in:
Maven Shade JavaFX runtime components are missing j What is actually in our custom jre? Not much:
$ customjre/bin/java --list-modules java.base@21 java.datatransfer@21 java.desktop@21 java.prefs@21 java.scripting@21 java.xml@21 jdk.unsupported@21
Zipping it up, it is 30.4mb is size (this was for an OS X x64 image). Your mouseMove-1.0.jar
that includes JavaFX, your app code and your dependencies is an additional 13.9mb. By comparison, an Azul JDK FX distribution for the same platform is 88.5mb.
The rest of this "answer" is not really an answer to your specific question title. Instead, it addresses some questions that arise when analyzing your question and in comment responses. Other readers (and you) may choose to ignore it.
Using a fat jar with JavaFX
I've created the fat jar which works perfectly from console: ( there is however this warning WARNING: Unsupported JavaFX configuration: classes were loaded from 'unnamed module @4efb10bd' )
While such a configuration can be made to work with JavaFX 21, it means that you are running an unsupported configuration which may not continue to work with future JavaFX distributions. For more information see:
Making JavaFX modules available
You can still build a JavaFX application based on fat jar in a supported way, but doing so has some implications:
Some ways you can make the JavaFX modules available:
To get the JavaFX deployment modules into your deployment package, you can either:
One disadvantage of using a prebuilt-runtime is the overall deployment will be larger as the prebuilt runtime may include packages you don't need (for instance if you don't use the javafx.web module, you don't need it, but it is quite large because it includes an entire Webkit implementation and JavaScript virtual machine). For many installations this additional size won't matter that much.
Note that JavaFX current depends on the AWT and Swing toolkits, so any runtime you ship that inclues JavaFX, will also include those toolkits.
So, one approach (which is kind of what you are initially trying in your question), would be to create a custom Java runtime that includes JavaFX.
Using jlink to create an image or runtime
Now, if you are going to be creating a custom runtime, in addition to including base JRE modules and JavaFX modules, you could also include additional 3rd party modules required by your application, and your application module, if your application is modular. If you do this, then you don't need and should not create a fat jar. Instead, you should specify a correct and complete module-info.java for your application and link all the required modules into the image using jlink.
When you do this jlink, will traverse the module graph and find the minimal set of modules needed to run your application and link them into the resultant runtime, creating the smallest possible runtime needed to execute your application. You do not need to use jdeps to determine which modules to links, jlink will find and link all of the required modules that were in the module graph and on the module path.
If you are using maven, the easiest way to accomplish this is to run the maven-javafx-plugin jlink target, which will create an image with the required JDK, JavaFX, application 3rd party dependencies and application modules, create a shell script to launch the application and zip the complete image up into a zip file for distribution. To use it, your end user can unzip the distribution and then run the application launcher shell script from the command line.
unzip app.zip
bin/app
If jlink is run with the --strip-native-commands
it won't include the Java command (instead your app is run using the generated shell script). But, if you don't use that switch, then you will get a java command which can be used to execute Java code the same way as for any other java runtime. Then you could do as you ask in your question:
$MY_CUSTOM_JVM/bin/java -jar mouseMove-1.0.jar
If that is what you really wanted. But, if your application is already in the custom image, instead of bundling a jar you could run it using:
java [options] -m module[/mainclass] [args ...]
This is how the prebuilt launcher works, for example, on my machine (OS X) the shell script for the app launcher is:
#!/bin/sh
JLINK_VM_OPTIONS=
DIR=`dirname $0`
$DIR/java $JLINK_VM_OPTIONS -m com.example.jlinkdem/com.example.jlinkdem.HelloApplication "$@"
Automatic modules
A restriction on jlink today is that the tool cannot link automatic modules. That means that for jlink to be able to link your entire project, every transitive dependency of the project needs to have a well-defined module-info. Unfortunately, many important 3rd party jar files do not include a module-info and cannot be linked by jlink. This means you need to exclude the non-modular jars from the jlink process, package them separately and ensure they are added to the classpath or modulepath. Tools like jpackage or complex maven assemblies may assist with this (discussion of how to do this is out of scope for my discussion here).
Understanding the issue with fat jars and the Java module system
When you create a fat jar using a tool such as the maven shade plugin or maven assembly plugin jar-with-dependencies option, all of the code from each of the dependencies is copied into a single jar file. Normally, that is fine if java modules are not involved, as all of the code in each jar would normally be added to the classpath.
But with modular jars, this is an issue. Each modular jar has, at its root a module-info which defines the module. If many jars are shaed into one, there is no way to combine this module-info into a single value which makes sense using the current Java module technology. So all of the module info in the dependent jars is lost and, your own code cannot be modular either because its module-info would depend on module-info which would have been provided in the dependent jars and no longer exists.
Hence Slaw's recommendation:
Don't include JavaFX in the fat JAR. Instead, include JavaFX in the custom run-time image you're trying to create. See this Q&A for more information regarding the warning.
And my recommendation:
You can’t jlink a “fat jar”. You can only jlink modules (jars which include module-info or jmods). When jlinking modules from the base jdk, jlink will use the jmods provided with the jdk (the jlink man page describes this process).
And also Slaw's description of the related issues:
There are a couple things to note about this:
- The jlink tool can only work with explicit modules (i.e., the dependency must have a module-info descriptor; automatic modules won't work).
- Your own code has a module-info descriptor, but you're trying to create a fat JAR file. Fat JARs are incompatible with modules, at least with the JAR File Specification in its current form (with custom module layers and/or class loaders, you can work around this problem—not necessarily trivial though).
- Named modules (i.e., code on the module-path) cannot directly reference code in the unnamed module (i.e., code on the class-path). If your code directly relies on any non-modular dependencies, then you cannot put your own code in the custom run-time image (for that reason, and because your code would have to be modular with requires on automatic modules, hitting point 1 in my previous comment). However, the unnamed module can use named modules. So, you can put modular dependencies in the custom run-time image, while having everything else on the class-path (e.g., a fat JAR).
Using a pre-created Java runtime to run another jar
As noted by Slaw in comment:
I think the OP's goal is to create a fat JAR, and then separately jlink a custom run-time image that includes only the Java/JDK modules needed by the fat JAR. They're trying to use jdeps to determine which Java/JDK modules are needed.
For which I recommend some of these options:
I'd suggest following openjfx.io instructions to create a custom JDK + JavaFX Image at openjfx.io, but don't supply the --bind-services
flag which may include a lot of unnecessary stuff. Then you get a minimal runtime upon which you could use a JavaFX app.
In the fat jar, don't include JavaFX components as noted by Slaw. If you need https or tls support, then also link in the cryptoki module. If something else breaks on execution due to a missing module you can add the additional module. This may occur because you use optional services such as cryptoki (even if you didn't know that you relied on such services) which a minimal jlink image (or even a jdeps code analysis) will not reveal as such services are loaded and used optionally at runtime, so the static linker won't know they are needed and to link them unless you explicitly tell it you need them.
To minimize the size of the generated image, you would also probably want to supply a --launcher
and set various options to remove unnecessary components (such as --no-header-files
, --no-man-pages
, --strip-debug
, --strip-native-commands
, etc).
Include required locales with --include-locales
, otherwise you will lose support for translation of some locale information.
While you can do the above from the command line, the maven-jlink-plugin can help if you use maven, or, if you use gradle, the badass-runtime-plugin can be used to package your app, as it performs a lot of dependency checks and configuration steps I mentioned for you, rather than having to manually specify the values at the command line. But, probably, either way would work.
For example azul zulu jre fx. It would include some modules you don't need but would may not really matter.
Regarding your dependencies and jdeps output
javax.mail
is obsolete. It might work OK with Java 9+ modules, or it may not (I don't know). It is still best to use a modern Java mail system (Angus Mail is the javax.mail
replacement). As you are using logback, this is detailed in the baeldung article: "Sending Emails with Logback".
However, you probably aren't sending emails with logback anyway, similarly you are also probably not using a servlet filter or binding to a naming directory using jndi while using logback. Those are all dependencies showing up in your jdeps analysis. This is because logback is a pluggable implementation. The core classes are compiled against those other libraries and can use them if it logback is configured to use them and the libraries are available at runtime. But if you don't need them you don't need to supply them. Therefore the jdeps analysis that you have run ends up being a bit confusing as it is referencing many potential dependencies which you don't actually need for your application and can just ignore.
FAQ
I am trying to see which jdk modules I am using in the jar so I can use jlink to create a custom runtime. The project is done in maven so I have other jars beside javafx ( apache, logging, etc ).
I do recommend if you do this that you use the most recent stable versions of all software (for instance logback 1.4.x based on jakarta, not obsolete jee software and slf4j 2.x which is compatible with java modules with well-defined module-info, unlike older slf4j versions).
If all the modules are well formed (they include a module-info), and your own project is modular (provides a module-info) this is actually a reasonably straightforward thing to do. You do not need jdeps for it, jlink will determine the minium modules needed and create an appropriate image. It might end up too minimum because it may exclude some optional services that your application may need to function, in which case you should ensure that you have those needed services specified as runtime dependencies in your maven dependency setup and require (or use) those services in your module info. See understanding modules for more info on consuming services.
If the modules are not well-formed or you have a non-modular application, then you could use jdeps
to determine the modules required, the --print-module-deps
option you set should work to do this, if you supply all other arguments correctly. For me, when I analyze a non-modular jar for dependencies, this option will determine a comma separated list of modules that can be passed to a jlink command. To do so, I set the module path and classpath arguments to jdeps so it knows where the dependent jars are, provide the jar to be analyzed as input and use the --ignore-missing-deps
command so that jdeps doesn't spam with warnings about optional services that aren't being used (such as the unused java mail and servlet api jars shown in your jdeps output).
requires com.sun.jna;
This implies that you are using jna for native access. That implies you have native components like DLLs or .so files. Which you can use those within the Java module system. Doing so is a complex and advanced topic which I will not address here.
Also jna is an autoamtic module that cannot be jlinked:
Error: automatic module cannot be used with jlink: com.sun.jna from file:///Users/<username>/.m2/repository/net/java/dev/jna/jna/5.13.0/jna-5.13.0.jar
I would like to know what jdk modules does logback use, jna, etc. And maybe in the future I will have more. If it's possible of course.
logback has a module info defined, it tells you what modules it uses.
Here are the requires portions:
requires transitive java.xml;
requires static java.sql;
// required by the optional SMTPAppenderBase component
requires static java.naming;
requires static janino;
requires static commons.compiler;
// transitive _imposes_ the presence of jakarta.mail on downstream users,
// let them declare it if they need it
requires static jakarta.mail;
// jakarta.servlet 5.0 is not modular
requires static jakarta.servlet;
// optionally require jansi
requires static org.fusesource.jansi;
You can see that most of the requirements are requires static
which makes them optional dependencies. You can deploy a logback app without supplying those dependent libraries (modules) if you don't need them - most people won't want most or all of those additional dependencies.
You can see what the actual dependencies are using the following command:
jdeps --print-module-deps --ignore-missing-deps logback-core-1.4.11.jar
Which outputs:
java.base,java.xml
So there you have the list of required dependencies. That is what jlink will include transitively when you link the the logback core module with anything else.
Now if you also needed some optional dependencies for your application, you could specify that in your application module-info (e.g. `uses , then run the same jdeps analysis on your application jar file with the module-info and it will show you what the dependencies are). Or you can add modules at the command line for jdeps, for instance to add a jndi naming interface so that lgoback could use it:
jdeps --print-module-deps --ignore-missing-deps --add-modules java.naming logback-core-1.4.11.jar
Then it shows you will need:
java.base,java.security.sasl,java.xml