I am preparing for a Java 11 certification exam and I still do not fully understand the new Java modules system: A training question gives a situation like the following:
In my app I have 2 jars, say A.jar and B.jar, where A.jar uses B.jar, but B.jar does not use A.jar.
Further, B.jar is modularized, while A.jar is not.
Now it sais, that if I try to run it like this:
java -classpath A.jar --module-path B.jar com.AMainClass
with a main class from A.jar, then it will fail to run - but why? For what I understood, when I put a non-modularized jar on the classpath, it becomes part of the "unnamed module", which should be able to see all the modules on the module path. So shouldn´t I be able to see the required dependencies from B.jar, when I run the main class from A.jar? Where is my misunderstanding?
It further sais, that it will work, if I put both jars on the module path and run the module instead of running the class:
java --module-path A.jar;B.jar --module A/com.AMainClass
Now A.jar, being on the module path, will become an automatic module and will also see the other module - ok, this is fine. But what is the difference between running a module and running a class? Why doesn´t it work when I try to run the class (as above) and what changes, when I run the module?
Related
I'm new to jar files and maven and trying to understand the use of Maven.
In this Maven in 5 minutes article, we create the my-app project using maven and the directory structure looks like this:
my-app
|-- pom.xml
-- src
|-- main
| -- java
| -- com
| -- mycompany
| -- app
| -- App.java
-- test
-- java
-- com
-- mycompany
-- app
-- AppTest.java
Then we use 1. mvn package followed by 2. java -cp target/my-app-1.0-SNAPSHOT.jar com.mycompany.app.App which runs our App.java class.
My understanding is that 1. creates a new 'target' directory and inside it the jar file. And we use 2. to run the App class using the jar file. My question is, why did we run the App class in this way? Why is it better than just going to src/main/java/com/mycompany/app and running java App.java ? Maybe I don't understand benefit of packaging. Thanks for any help.
To know the answer of Why, you need to understand following things first:
How to compile a Java program and how to run the compiled java program (especially program declared inside a package)
If you have java class Test inside package hello, you can compile it using javac hello\Test.java and run it using java hello.Test (i.e. we use fully qualified name to run a program and can not run it like java Test, it will give you error "could not find or load main class Test")
So, just going to src/main/java/com/mycompany/app and running java App.java won't work but from src/main/java folder you can run it like com.mycompany.app.App
What is jar packaging.
jar i.e. Java archive is nothing but a zip file aggregating all the java classes so that it can be distributed as a single unit.
Classpath
Classpath is the place where java will look for the compiled classes
How to run a program which depends on Java classes in other jar files
Suppose if my Test class depends on class XYZ which is inside the
abc.jar file, then we need to tell java that search this abc.jar for
dependencies (include this jar in classpath). This can be done using command java -cp abc.jar hello.Test here -cp option is nothing but a classpath and is used to tell java about directories or archives in which classes could be found. This command can be used when Test class is inside jar file like in your case
Maven
If you have understood the above things then you would know that Maven has nothing to do with running your program. It is just a build tool which helps build the jar file from you code and helps in executing/organizing different tasks apart from build like clean, running tests, etc.
mvn package
Maven is one of the package management tools for java.. the flag package, packages the app. It's used to declare deps for a project, package a project, build a project, and install binaries to a place(~/.m2/xx/..) where they can be read by java processes.
java -cp target/my-app-1.0-SNAPSHOT.jar
Runs the app, invoke main class of the app, -cp stands for classPath
Maven not just a packaging tool but also managing dependencies.
If your project is simple with no dependency/no import other package, you can "just going to src/main/java/com/mycompany/app and running java App.java". If it is not the case, you can't do it (try it)
maven package will export/copy dependencies to target folder so that JDK can find required classes and you can use it easily
I created a JavaFX project in IntelliJ.
I can run project in IntelliJ. I added below code in Configurations):
--module-path ${PATH_TO_FX} --add-modules=javafx.controls,javafx.fxml
But the output .jar file of project (made with Artifects) doesn't run. I tested these commands, but didn't get any chance:
java --module-path %PATH_TO_FX% --add-modules javafx.controls,javafx.fxml -jar Timer.jar
java --module-path %PATH_TO_FX% --add-modules javafx.controls Timer.jar
Last error log of command line:
Error: Could not find or load main class Files\Java\javafx-sdk-11.0.1\lib
Caused by: java.lang.ClassNotFoundException: Files\Java\javafx-sdk-11.0.1\lib
p.s: I could run .jar file of this project when build on JDK-10
EDIT:
I downloaded JavaFX and added it's lib folder to System Environments.
for adding JavaFX to project I did this process:
Project Structure > Libraries > add > Java > JavaFxPath/lib
Then I created Artifect for output jar file in this process:
Project Structure > Artifects > Add > JAR > From Modules with dependencies > main Class : main.Main.
Providing you have a simple (non-modular) JavaFX 11 project (without Maven/Gradle build tools), and you are using IntelliJ, like the HelloFX sample from here,
this is how you can create a jar from IntelliJ that can be run from the console
A full tutorial on how to run the project can be found here, and instructions on how to create a jar are here (see section Non-modular project), but these doesn't cover Artifacts from IntelliJ.
Check that the HelloFX project runs from IntelliJ with these VM options:
--module-path ${PATH_TO_FX} --add-modules javafx.controls,javafx.fxml
where PATH_TO_FX has been set in File -> Settings -> Appearance & Behavior -> Path Variables, pointing to the JavaFX SDK lib.
Semi fat Jar
We can create a Jar that only contains the classes from the project, and third party dependencies, but not JavaFX ones.
Go to File -> Project Structure -> Artifacts -> Add -> JAR -> From modules with dependencies, add your main class, accept.
Then remove the JavaFX jars from the list, and accept.
Build the project, it will create a quite small jar (3 KB in this case).
Now you should be able to run it like:
java --module-path %PATH_TO_FX% --add-modules javafx.controls,javafx.fxml -jar out\artifacts\HelloFX_jar\HelloFX.jar
(make sure that %PATH_TO_FX% points to a valid folder and use quotes if it contains spaces.
You can distribute this jar, and run it in other platforms, providing those also have the JavaFX SDK.
Fat Jar
If you want a full fat jar that includes JavaFX dependencies, you can still use Artifacts.
Go to File -> Project Structure -> Artifacts -> Add -> JAR -> From modules with dependencies, add your main class, accept.
Then keep the JavaFX jars from the list, and accept. Build the project.
In theory, you should be able to run it like:
java -jar out\artifacts\HelloFX_jar\HelloFX.jar
But this won't work.
Reason 1: You need a launcher class, as explained here.
So create a launcher class:
public class Launcher {
public static void main(String[] args) {
Main.main(args);
}
}
Reason 2: If you only add your SDK jars to the fat jar, you will be missing the native libraries, as explained here.
So edit the artifact, select the Launcher class as main class, and add the native libraries (Directory Content -> path-to/JavaFX SDK/bin on Windows):
Now build the project (now the jar is about 33 MB, and contains unnecessary native libraries) and run:
java -jar out\artifacts\HelloFX_jar\HelloFX.jar
You can distribute this jar, but only to Windows platforms.
You can create similar jars for other platforms, if you download their JavaFX SDKs, and you can also build cross-platform jars if you add them all together, as explained in the linked answers above.
Anyway, you should consider using jlink instead.
Note
About this error:
Caused by: java.lang.ClassNotFoundException: Files\Java\javafx-sdk-11.0.1\lib
it looks like the library path was set without quotes and it is missing the first part of the path C:\Program Files\.... Just make sure you use quotes:
set PATH_TO_FX="C:\Program Files\Java\javafx-sdk-11.0.1\lib"
I had the similar issue exporting/generating an Jar using JavaFX and IntelliJ Non-modular with Gradle (https://openjfx.io/openjfx-docs/)
The jar I was generating using Gradle jar command does not run and throws error saying it can not find my Main Class. When I opened my jar I was able to locate my main class. So I realized the error has to do with Jar Packaging.
I fixed the problem by adding JavaFX SDK to my Java SDk in IntelliJ as shown below.
After this I use the regular Gradle build Jar command to generate my Jar file (as shown below) and it runs Normally.
The easiest way to do this is to use an OpenJDK build that include JavaFX. Both Bellsoft and Azul produce such builds.
For Azul's Zulu builds of OpenJDK:
https://www.azul.com/downloads/zulu-community/?version=java-11-lts&package=jdk-fx
For Bellsoft Liberica JDK:
https://bell-sw.com/pages/downloads/#/java-11-lts
and choose "Full JDK"
These builds are basically OpenJDK with the OpenJFX JavaFX modules added. Though be careful as some aspects of JavaFX may not be supported on LibericaFX. See https://bell-sw.com/pages/liberica-release-notes-11.0.9.1/
The above answers apply to non-modular JavaFX projects. To get a modular JavaFX project (with modular dependencies) running I used jlink.
Take the JavaFX SDK from https://gluonhq.com/products/javafx/; unzip the files to a directory, and point a shell variable JFX_LIB to the unzipped lib/ directory.
Take the JavaFX jmods (not SDK) file from https://gluonhq.com/products/javafx/; unzip the jmods, they are in a folder similar to javafx-jmods-11.0.2/. Point a shell variable JMOD_PATH to the unzipped directory javafx-jmods-11.0.2/.
Compile, sending your compiled classes to the directory mods/:
javac -d mods --module-source-path src --module-path $JFX_LIB/javafx.graphics.jar:$JFX_LIB/javafx.base.jar:$JFX_LIB/javafx.controls.jar --module com.mymodule
Create a custom JRE with jlink, referring to the module path containing the JavaFX mods and your own compiled mod:
jlink --module-path $JMOD_PATH:mods --add-modules com.mymodule --output customjre
Run:
customjre/bin/java --module com.mymodule/com.mymodule.Main
(I tried for a LONG TIME to get a java -jar running with --module-path and --add-modules. I always got the following error:)
Error: Could not find or load main class com.mymodule.Main
Caused by: java.lang.NoClassDefFoundError: javafx/application/Application
I'll try to boil my issue down into a basic external JAR linking question, which I have not been able to find an example/answer for. I have 2 JARs, a.jar and b.jar, in the same directory. The MANIFEST.MF of a.jar contains: Class-Path: b.jar.
Essentially, I want to run a.jar that contains minimal application-level classes, but links to a large external b.jar with all other necessary classes. But running the command java -jar a.jar results in: Exception in thread "main" java.lang.NoClassDefFoundError: com/example/MainApp.
Not sure if it's relevant, but b.jar is actually a Spring Boot JAR which contains the expected classes (e.g. com/example/MainApp.class) in BOOT-INF/classes. The MANIFEST.MF of b.jar contains: Spring-Boot-Classes: BOOT-INF/classes/.
I want to know if there's a simple way to achieve this? Let me know if any more details are needed to diagnose the issue.
Update:
I copied the com/example/MainApp.class file to the base directory in b.jar, and the class was found! But I want to keep the original files in place. So I suppose that rephrases the question: how do you specify where classes are located inside the JAR?
did you try something like that path like "classpath: path/to/jar/b.jar"
I have run into a snag with the JPMS jdk.internal.loader.BuiltinClassLoader when executing main methods in the context of my IDE (I use IntelliJ 2018.1).
If a resource is loaded in an in module class using a standard method like
MyMainClass.class.getResourceAsStream("/some-resource")
The resource is not found because the ModuleReference is not a jar, but a class path, e.g.
[module org.ubl.scb, location=file:///home/christopher/IdeaProjects/systematik-catalogue-builder/web-anno/out/production/classes/]
All of the other entries in the nameToModule Map look like this:
key = "logback.core"
value = "[module logback.core, location=file:///home/christopher/.m2/repository/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar]"
Of course, a resource will never be found in classes, but it could be if the location is the jar root.
Is this a bug or am I missing something?
This is an IDE related compiler output issue. The project uses a gradle build. The default output for a gradle build will create a directory structure like this:
build
-classes
-java
-main
module-info.class
-libs
-resources
However, to run tests or to execute main methods in a JPMS project, gradle does not work (yet). I use the built-in IntelliJ compiler which generates a completely different output structure than gradle. The default looks like this:
out
-production
-classes
-some/package/name
some-resource
module-info.class
One must rebuild a project using the Build Project (ctrl + F9) to generate this output structure if something changes. In this case, I suppose that the missing "some-resource" had not yet been copied into the IntelliJ output structure (probably because I had not rebuilt the project yet), so the BuiltinClassLoader would not find it there. It is perhaps confusing that resources are copied into a directory called "classes", (which is identical to the JAR), but that is how it is done.
The Gradle build output is not used at all by the IDE... This also has consequences for any compiler options like --add-modules that must be added to the IDE settings, even if they are specified in the gradle build script.
...
When I compile a module that depends on other modules I've compiled previously I have to specify the --module-path <directory> option. This makes modules I depend on visible.
But at the same time I would also like to make some non-modular Jar files visible. However if don't make them automatic modules and just specify the --class-path some.jar right next to --module-path <directory>, then javac seems to ignore the claspath and throws "package yyy not found" and other "not found" errors.
I can understand that using --class-path and --module-path at the same (compile) time is illegal, but javac does not warn me against it in any way.
You can use class path and module path in parallel, but there are a few details to consider.
Dependency Module Path ~> Class Path
Explicit modules (JARs with a module descriptor on the module path) can not read the unnamed module (JARs on the class path) - that was done on purpose to prevent modular JARs from depending on "the chaos of the class path".
Since a module must require all of its dependencies and those can only be fulfilled by other named modules (i.e. not JARs on the class path) all dependencies of a modular JAR must be placed on the module path. Yes, even non-modular JARs, which will then get turned into automatic modules.
The interesting thing is that automatic modules can read the unnamed module, so their dependencies can go on the class path.
Dependency Class Path ~> Module Path
If you compile non-modular code or launch an application from a non-modular JAR, the module system is still in play and because non-modular code does not express any dependencies, it will not resolve modules from the module path.
So if non-modular code depends on artifacts on the module path, you need to add them manually with the --add-modules option. Not necessarily all of them, just those that you directly depend on (the module system will pull in transitive dependencies) - or you can use ALL-MODULE-PATH (check the linked post, it explains this in more detail).
I believe using the --classpath and --module-path options at the same time is not illegal. It's possible to use both at the same time as even when you don't explicitly specify a classpath it defaults to the current directory.
Details from the javac -help message and javac tools docs -
--module-path <path>, -p <path>
Specify where to find application modules
--class-path <path>, -classpath <path>, -cp <path>
Specify where to find user class files and annotation processors
If --class-path, -classpath, or -cp aren’t specified, then the user
class path is the current directory.
Edit: Thanks to #MouseEvent, I'd probably missed out the part in the question
However if don't make them automatic modules and just specify the
--class-path some.jar right next to --module-path , then javac seems to ignore the claspath and throws "package yyy not found"
and other "not found" errors.
If you don't make them automatic, it's treated as an Module System's unnamed module and -
A named module cannot, in fact, even declare a dependence upon the
unnamed module. This restriction is intentional, since allowing named
modules to depend upon the arbitrary content of the class path would
make reliable configuration impossible.
Moreover, the unnamed module exports all of its packages hence the code in an automatic modules will be able to access any public type loaded from the classpath.
But an automatic module that makes use of types from the classpath must not expose those types to the explicit modules that depend upon it, since explicit modules cannot declare dependencies upon the unnamed module.
If code in the explicit module com.foo.app refers to a public type
in com.foo.bar, e.g., and the signature of that type refers to a
type in one of the JAR files still on the class path, then the code
in com.foo.app will not be able to access that type since
com.foo.app cannot depend upon the unnamed module.
This can be remedied by treating com.foo.app as an automatic module temporarily so that its code can access types from the class path, until such time as the relevant JAR file on the class path can be treated as an automatic module or converted into an explicit module.