Does Java EE / Jakarta EE support the Java module system? Is it possible to make web application with the Java module system?
Asked Answered
B

2

5

I'm building my first modular app with Java 11 and Maven 3.6.1. My IDE is IntellijIDEA 2019.1.3. I added a module 'app' and added module-info.java, but I'm confused because my app is working even I added spring dependencies to the app module and I didn't open my module or some package in the module for reflection.

I added module-info.java with my IDE's feature and it forces me to add requires statements. So far so good. But why it works without opening the module for reflection? Is that some new feature in Java 11 or in my IDE version? Am I doing something wrong?

My module-info.java:

module app {
    requires spring.web;
    requires spring.webmvc;
    requires javax.servlet.api;
    requires spring.context;
}

I tried to find the answer on SO and JetBrains, but I failed.

I am looking at this guy (https://youtu.be/hxsCYxZ1gXU?t=2238) working with spring modules and his IDE requires him to open the module for reflection. And I downloaded his project and deleted opens statements and it still compiles in my IDE.

And one more question: How can I examine the module-path with my IDE? How can I see which modules are there and what is on my classpath (right now there should be nothing)?

EDIT: I just realized that my IDE prints Command line argument: --add-opens=java.base/java.lang=ALL-UNNAMEDlog. I suppose this is the reason, but I can't find where this arg is coming from. Am I on the right trail?

EDIT No. 2: In the console log, as command-line arguments are:

--add-opens=java.base/java.lang=ALL-UNNAMED
--add-opens=java.base/java.io=ALL-UNNAMED
-Djava.util.logging.config.file=C:\Users\Nedim\.IntelliJIdea2019.2\system\tomcat\Unnamed_ems\conf\logging.properties
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
-Djdk.module.showModuleResolution=true
-Dcom.sun.management.jmxremote=
-Dcom.sun.management.jmxremote.port=1099
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.password.file=C:\Users\Nedim\.IntelliJIdea2019.2\system\tomcat\Unnamed_ems\jmxremote.password
-Dcom.sun.management.jmxremote.access.file=C:\Users\Nedim\.IntelliJIdea2019.2\system\tomcat\Unnamed_ems\jmxremote.access
-Djava.rmi.server.hostname=127.0.0.1
-Djdk.tls.ephemeralDHKeySize=2048
-Djava.protocol.handler.pkgs=org.apache.catalina.webresources
-Dignore.endorsed.dirs=
-Dcatalina.base=C:\Users\Nedim\.IntelliJIdea2019.2\system\tomcat\Unnamed_ems
-Dcatalina.home=D:\tomcat\apache-tomcat-9.0.21
-Djava.io.tmpdir=D:\tomcat\apache-tomcat-9.0.21\temp

Also, from my log (I'm not sure is this relevant):

D:\tomcat\apache-tomcat-9.0.21\bin\catalina.bat run
[2019-11-08 05:42:34,404] Artifact app:war exploded: Waiting for server connection to start artifact deployment...
Using CATALINA_BASE:   "C:\Users\Nedim\.IntelliJIdea2019.2\system\tomcat\Unnamed_ems"
Using CATALINA_HOME:   "D:\tomcat\apache-tomcat-9.0.21"
Using CATALINA_TMPDIR: "D:\tomcat\apache-tomcat-9.0.21\temp"
Using JRE_HOME:        "C:\Program Files\Java\jdk-11.0.5"
Using CLASSPATH:       "D:\tomcat\apache-tomcat-9.0.21\bin\bootstrap.jar;D:\tomcat\apache-tomcat-9.0.21\bin\tomcat-juli.jar"

--show-module-resolution prints out:

root java.sql jrt:/java.sql
root jdk.management.jfr jrt:/jdk.management.jfr
root java.rmi jrt:/java.rmi
root jdk.jdi jrt:/jdk.jdi
root java.transaction.xa jrt:/java.transaction.xa
root java.xml.crypto jrt:/java.xml.crypto
root java.logging jrt:/java.logging
root java.xml jrt:/java.xml
root jdk.xml.dom jrt:/jdk.xml.dom
root jdk.jfr jrt:/jdk.jfr
root java.datatransfer jrt:/java.datatransfer
root jdk.httpserver jrt:/jdk.httpserver
root jdk.net jrt:/jdk.net
root java.naming jrt:/java.naming
root java.desktop jrt:/java.desktop
root java.prefs jrt:/java.prefs
root java.net.http jrt:/java.net.http
root jdk.compiler jrt:/jdk.compiler
root java.security.sasl jrt:/java.security.sasl
root jdk.jconsole jrt:/jdk.jconsole
root jdk.attach jrt:/jdk.attach
root java.base jrt:/java.base
root jdk.javadoc jrt:/jdk.javadoc
root jdk.management.agent jrt:/jdk.management.agent
root jdk.jshell jrt:/jdk.jshell
root jdk.jsobject jrt:/jdk.jsobject
root java.sql.rowset jrt:/java.sql.rowset
root java.management jrt:/java.management
root jdk.sctp jrt:/jdk.sctp
root java.smartcardio jrt:/java.smartcardio
root jdk.unsupported jrt:/jdk.unsupported
root jdk.scripting.nashorn jrt:/jdk.scripting.nashorn
root java.instrument jrt:/java.instrument
root java.security.jgss jrt:/java.security.jgss
root jdk.management jrt:/jdk.management
root jdk.security.auth jrt:/jdk.security.auth
root java.compiler jrt:/java.compiler
root java.scripting jrt:/java.scripting
root jdk.dynalink jrt:/jdk.dynalink
root jdk.unsupported.desktop jrt:/jdk.unsupported.desktop
root jdk.accessibility jrt:/jdk.accessibility
root jdk.jartool jrt:/jdk.jartool
root java.management.rmi jrt:/java.management.rmi
root jdk.security.jgss jrt:/jdk.security.jgss
jdk.security.jgss requires java.security.sasl jrt:/java.security.sasl
jdk.security.jgss requires java.logging jrt:/java.logging
jdk.security.jgss requires java.security.jgss jrt:/java.security.jgss
java.management.rmi requires java.naming jrt:/java.naming
java.management.rmi requires java.rmi jrt:/java.rmi
java.management.rmi requires java.management jrt:/java.management
jdk.accessibility requires java.desktop jrt:/java.desktop
jdk.unsupported.desktop requires java.desktop jrt:/java.desktop
jdk.dynalink requires java.logging jrt:/java.logging
jdk.security.auth requires java.naming jrt:/java.naming
jdk.security.auth requires java.security.jgss jrt:/java.security.jgss
jdk.management requires java.management jrt:/java.management
java.security.jgss requires java.naming jrt:/java.naming
jdk.scripting.nashorn requires jdk.dynalink jrt:/jdk.dynalink
jdk.scripting.nashorn requires java.logging jrt:/java.logging
jdk.scripting.nashorn requires java.scripting jrt:/java.scripting
java.sql.rowset requires java.logging jrt:/java.logging
java.sql.rowset requires java.sql jrt:/java.sql
java.sql.rowset requires java.naming jrt:/java.naming
jdk.jsobject requires java.desktop jrt:/java.desktop
jdk.jshell requires jdk.compiler jrt:/jdk.compiler
jdk.jshell requires java.compiler jrt:/java.compiler
jdk.jshell requires java.logging jrt:/java.logging
jdk.jshell requires java.prefs jrt:/java.prefs
jdk.jshell requires jdk.internal.ed jrt:/jdk.internal.ed
jdk.jshell requires jdk.internal.le jrt:/jdk.internal.le
jdk.jshell requires jdk.internal.opt jrt:/jdk.internal.opt
jdk.jshell requires jdk.jdi jrt:/jdk.jdi
jdk.management.agent requires java.management jrt:/java.management
jdk.management.agent requires java.management.rmi jrt:/java.management.rmi
jdk.javadoc requires java.compiler jrt:/java.compiler
jdk.javadoc requires java.xml jrt:/java.xml
jdk.javadoc requires jdk.compiler jrt:/jdk.compiler
jdk.attach requires jdk.internal.jvmstat jrt:/jdk.internal.jvmstat
jdk.jconsole requires java.desktop jrt:/java.desktop
jdk.jconsole requires jdk.management.agent jrt:/jdk.management.agent
jdk.jconsole requires jdk.attach jrt:/jdk.attach
jdk.jconsole requires java.management.rmi jrt:/java.management.rmi
jdk.jconsole requires java.management jrt:/java.management
jdk.jconsole requires jdk.internal.jvmstat jrt:/jdk.internal.jvmstat
jdk.jconsole requires jdk.management jrt:/jdk.management
jdk.jconsole requires java.rmi jrt:/java.rmi
java.security.sasl requires java.logging jrt:/java.logging
jdk.compiler requires java.compiler jrt:/java.compiler
java.prefs requires java.xml jrt:/java.xml
java.desktop requires java.xml jrt:/java.xml
java.desktop requires java.prefs jrt:/java.prefs
java.desktop requires java.datatransfer jrt:/java.datatransfer
java.naming requires java.security.sasl jrt:/java.security.sasl
jdk.xml.dom requires java.xml jrt:/java.xml
java.xml.crypto requires java.xml jrt:/java.xml
java.xml.crypto requires java.logging jrt:/java.logging
jdk.jdi requires jdk.attach jrt:/jdk.attach
jdk.jdi requires jdk.jdwp.agent jrt:/jdk.jdwp.agent
java.rmi requires java.logging jrt:/java.logging
jdk.management.jfr requires jdk.jfr jrt:/jdk.jfr
jdk.management.jfr requires java.management jrt:/java.management
jdk.management.jfr requires jdk.management jrt:/jdk.management
java.sql requires java.logging jrt:/java.logging
java.sql requires java.transaction.xa jrt:/java.transaction.xa
java.sql requires java.xml jrt:/java.xml
jdk.dynalink binds jdk.scripting.nashorn jrt:/jdk.scripting.nashorn
java.naming binds jdk.naming.rmi jrt:/jdk.naming.rmi
java.naming binds jdk.naming.dns jrt:/jdk.naming.dns
java.management binds java.management.rmi jrt:/java.management.rmi
java.management binds jdk.management jrt:/jdk.management
java.management binds jdk.management.jfr jrt:/jdk.management.jfr
java.compiler binds jdk.compiler jrt:/jdk.compiler
java.compiler binds jdk.javadoc jrt:/jdk.javadoc
java.base binds jdk.zipfs jrt:/jdk.zipfs
java.base binds jdk.localedata jrt:/jdk.localedata
java.base binds jdk.security.jgss jrt:/jdk.security.jgss
java.base binds java.security.jgss jrt:/java.security.jgss
java.base binds jdk.crypto.cryptoki jrt:/jdk.crypto.cryptoki
java.base binds java.smartcardio jrt:/java.smartcardio
java.base binds jdk.crypto.mscapi jrt:/jdk.crypto.mscapi
java.base binds jdk.crypto.ec jrt:/jdk.crypto.ec
java.base binds java.security.sasl jrt:/java.security.sasl
java.base binds java.naming jrt:/java.naming
java.base binds java.xml.crypto jrt:/java.xml.crypto
java.base binds jdk.jdeps jrt:/jdk.jdeps
java.base binds jdk.javadoc jrt:/jdk.javadoc
java.base binds jdk.jlink jrt:/jdk.jlink
java.base binds jdk.jartool jrt:/jdk.jartool
java.base binds jdk.compiler jrt:/jdk.compiler
java.base binds java.desktop jrt:/java.desktop
java.base binds java.management jrt:/java.management
java.base binds jdk.security.auth jrt:/jdk.security.auth
java.base binds java.logging jrt:/java.logging
java.base binds jdk.charsets jrt:/jdk.charsets
jdk.jshell binds jdk.editpad jrt:/jdk.editpad
java.desktop binds jdk.accessibility jrt:/jdk.accessibility
java.desktop binds jdk.unsupported.desktop jrt:/jdk.unsupported.desktop
java.datatransfer binds java.desktop jrt:/java.desktop
java.scripting binds jdk.scripting.nashorn jrt:/jdk.scripting.nashorn
jdk.internal.jvmstat binds jdk.jstatd jrt:/jdk.jstatd
jdk.jstatd requires jdk.internal.jvmstat jrt:/jdk.internal.jvmstat
jdk.jstatd requires java.rmi jrt:/java.rmi
jdk.editpad requires java.desktop jrt:/java.desktop
jdk.editpad requires jdk.internal.ed jrt:/jdk.internal.ed
jdk.jlink requires jdk.jdeps jrt:/jdk.jdeps
jdk.jlink requires jdk.internal.opt jrt:/jdk.internal.opt
jdk.jdeps requires java.compiler jrt:/java.compiler
jdk.jdeps requires jdk.compiler jrt:/jdk.compiler
jdk.crypto.cryptoki requires jdk.crypto.ec jrt:/jdk.crypto.ec
jdk.naming.dns requires java.naming jrt:/java.naming
jdk.naming.rmi requires java.naming jrt:/java.naming
jdk.naming.rmi requires java.rmi jrt:/java.rmi
Blackberry answered 6/11, 2019 at 19:45 Comment(19)
The command line might just be a part of the application configuration. Also, check the Java Compiler section under Build, Execution and Deployment tab in Preferences. You could try listing the modules resolved during the startup by adding the --show-module-resolution flag.Affenpinscher
I already did that and couldn't find any flags there. Essentially, my project works but I do not like some 'magic' doing stuff I am not aware of. Maybe it is a good thing (why would IDE put it as default if not), but as a developer, I want to know what is happening behind the scene.Blackberry
I tried --show-module-resolution flag, but I was wondering is there some UI in IntelliJ where you can see what is on your module-path. And why can't I see any spring modules listed with --show-module-resolution?Blackberry
There is a significant difference between “it works” and “it still compiles in my IDE”. The latter does not imply the former. Since you already understood that opening the module will enable Reflection at runtime, you should also understand that not opening the module will cause failures to happen at runtime. If an IDE knows beforehand that a particular module will use reflection at runtime and not opening your module to it will cause failures, it is helpful, but if another IDE fails to recognize the problem at compile-time, it doesn’t imply that the problem is gone.Wilonah
Exactly that is the reason why I want to disable automatically module opening by IDE, but I still have no idea how...Blackberry
But that command line is opening java.base to unnamed modules, it’s not opening your module to other modules.Wilonah
Thank you for helping! I see your point. You are saying my modules are still encapsulated. But I don't get how Spring or Hibernate can reflectively access to my class if it is not exposed. What am I missing? I know that java.base is a root of the JDK modular platform and it is transitively part of every module. But how framework rich my class?Blackberry
Since you said you don’t see any spring modules listed with --show-module-resolution, check whether spring truly has been specified via module-path rather than class-path. That’s a common source of confusion, modular code often runs without problems when being specified on the class path, but there’s (almost) no encapsulation then.Wilonah
And how can I check that? I already asked how could I check what is on module-path and what is on class-path. The only recommendation I got is to use --show-module-resolution as JVM argument. And Spring has not mentioned there at all. The log is too long to be put in the comment. When I select the 'app' module in my project (it is only one currently and its task is to bring Spring context), on the Structure tab in Intellij I can see something like this: Libraries --> <11>, Maven:org.springframework:spring-core:5.2.0, ... and then Module Dependencies <--- empty folderBlackberry
Shouldn't module-info.java put the module on module-path!?Blackberry
No, the placement of this source code file is irrelevant. Even the presence of the compiled module-info.class is irrelevant when it is present on the class path. It has to be on the module path, which is a distinct runtime option, e.g. on the command line, it’s --class-path vs --module-path. A countercheck to --show-module-resolution is to print the class path, e.g. put System.out.println("classpath: "+System.getProperty("java.class.path")); into your application. It should be empty or at least not referring to Spring jars. Otherwise, it’s not a module resp. in the unnamed module.Wilonah
Thank you very much, I already learned much from you. I really appreciate it. Regarding your recommendation, I got this: ``` classpath: D:\tomcat\apache-tomcat-9.0.21\bin\bootstrap.jar;D:\tomcat\apache-tomcat-9.0.21\bin\tomcat-juli.jar ``` Nothing else. See also EDIT No.2 in my post - I added command-line arguments from the log.Blackberry
So there is no --module-path in the log at all and consistently, the module resolution doesn’t even show your own app module. So I suppose, Tomcat is loading your code, as well as Spring, through a custom class loader, that’s why neither appears in the class path. However, loading via custom class loader can be done modular or old style. Mind that a simple System.out.println(SomeClass.class.getModule()); will tell you whether it belongs to a declared module or the unnamed module. You can do it for one of your classes, as well as a Spring specific class…Wilonah
Oh, man :) where Tomcat's custom loader came from :) I did what you recommended and put one sout line to WebbAppInitializer.class which brings up Spring context and also one to TestController.class and got this two logs: >>> Logging from WebbAppInitializer.class: unnamed module @43c2e470 and >>> Logging from TestController.class: unnamed module @43c2e470. Since they are both in the app module, the same mamory location is printed in both cases. So you supposed well! And what would be the right thing to do regarding this situation, what options do I have?Blackberry
PS. Thank you for all your patience and helping. I really mean it. It means a lot to me, obviously :) I don't think I would come to this on my own.Blackberry
My brain has turned to mush... I googled a lot and tried a lot... I installed Tomcat 9.0.27, set Intellij to use it, edit env variables... and big nothing happened. I hope you also have a solution for this... It's killing me...Blackberry
If I knew that Tomcat doesn't support it, I said it right in the first comment. Unfortunately, that's the thing I didn't know. So the symptoms guided us to precisely that conclusion, the software hasn't been loaded as module, but regarding whether there is no support in general, we have to trust the Tomcat/EE experts.Wilonah
Thank you @Wilonah for all help and advice. I am really filling sh**ty now. Does this means that we can't use java modularity in the right way in web applications? What about this guy here youtu.be/u4VV9NSK_0Y?t=2320 - at this point, you can see his log and Tomcat there and his modular system works correctly... But when I download his project I have the same issue. And is there any other advice you can give me or should I just give up on java modularity? Isn't it the future of Java?Blackberry
JavaEE always was behind the core Java, which is not surprising given that it settles on it and also requires the container vendors to catch up. So I suppose, if it doesn't exist today, modular EE will come one day. But if that guy managed to use it already today, try to contact that guy and ask him.Wilonah
B
7

Let summarize the problem and the answer.

I renamed my question to better suit the problem, so in the future, it can be reused. When I start this thread question was: Do I have to add an open statement in module-info.java to open module for reflection. What happened is that I made modular web application with Java 11 and Spring and I have noticed that my setup does not require open modules or any packages in them for reflection.

Using --show-module-resolution flag listed only JDK modules. It didn't show any of my modules.

User @Holger helped the most with this issue,. He recommended using System.out.println("classpath: " + System.getProperty("java.class.path")); to see what is on the classpath. And there was nothing relevant.

Then he proposed using System.out.println(SomeClass.class.getModule()); somewhere in the code to see is my code belongs to a declared module or unnamed module. And it turned out that it belongs to the unnamed module.

So the conclusion was that Apache Tomcat's ClassLoader loads my classes as well as Spring jars to the unnamed module.

Then I asked Apache support how can I make Tomcat load my modules correctly and the answer was:

You can't. The Servlet API (nor any part of Java EE / Jakarta EE) does not make use of the module system. Mark

But I still couldn't let it go, because I saw the guy named Jaap Coomans on Youtube working with modules and he was using Tomcat. He put his contact on one of the last slides, so I decided to ask him what is going on. And here is a relevant part of his answer:

From what I get from your description the main difference between your setup and the one in my talk is that your are running a Spring web application on a separate Tomcat server, whereas in my talk I'm building a Spring Boot application with an embedded Tomcat server. This is a subtle difference that is likely to be the cause of the issue you're running into. With Spring Boot, the application itself is booted from the command line. The application then launches the embedded Tomcat, making the application module itself in charge of classloading and the classpath and modulepath. Most of this is managed under the hood by Spring Boot, so you could say Spring (Boot) is in charge of booting Tomcat. The approach you've taken is the "classic" Spring (without Boot) setup where Tomcat starts the process and is in charge of classloading and loads your application. To put it a little simpler: in the example of my talk Spring starts Tomcat, in your case it is the other way around.

(...)

It is true that currently the Java / Jakarta EE spec does not support the module system. Due to the nature of the spec I guess this may actually be quite of a challenge to overcome. I'm not sure where this topic is on the roadmap of Jakarta EE right after the transition to the Eclipse foundation. That's something I will definitely look out for. Unfortunately the answer to your actual question is still that it is not possible in your setup.

To key takeaways here are:

  • The core Java supports the Java module system.
  • Java EE / Jakarta EE does not make use of the Java module system, so the web container will put all classes to an unnamed module. That is happening with a separate Tomcat setup.
  • As Jaap stated, Spring Boot is using embedded Tomcat, making the application module itself in charge of classloading and the classpath and module-path.

And in the end, after I got an answer from @Holger and Jaap I found this and one interesting possible solution. I haven't tried it.

It was a long road, but we made it clear in the end.

Blackberry answered 13/11, 2019 at 9:14 Comment(3)
I'm having the same problem, but with Wildfly: #66178738 . Did you have success solving your problem?Leroylerwick
@Leroylerwick not actually... as I wrote above, it seems to be impossible within the standard Spring project. Spring Boot on the other hand spans its own Tomcat server and enables usage of Java modular system. That is why I convert the project to Spring Boot and there are still issues with split packages using third party packages like Mongo ORM or Sendgrid... in this end, I just gave up and removed module-info files, and now it works like charm :)Blackberry
I also got an official answer from the Wildfly guys saying that for the time being this is also not possible with Wildfly, as it runs on classpath mode and there's no simple way to change this: groups.google.com/g/wildfly/c/lTj1suvUpYg . Sad that there's such a nice feature around and application servers aren't still prepared for it. Thank you for the answer.Leroylerwick
I
1

Yes. You need to explicitly allow reflection using 'opens' directive.

You can expose package for reflection to specific module using 'opens to'

module org.util.web.server {

    exports org.util.web.server;
    opens org.util.web.server to org.jetbrains.annotations;
}

or You can expose package for reflection to all modules using

module org.util.web.server {

    exports org.util.web.server;
    opens org.util.web.server;
}
Instrumental answered 7/11, 2019 at 12:42 Comment(1)
Thank you for your answer. It clarifies that things in java modular platform did not change regarding module encapsulation. That is something I have learned from Koushnik's book on Java 9 modularity. And it seems like my IDE adds --open-module flag. I would like to disable that and be more explicit, but I can't find that in the setting (compiler, java compiler...).Blackberry

© 2022 - 2024 — McMap. All rights reserved.