I have the following project structure.
ProjectName
|
|---src
|
|---main
|
|---java
| |
| |---ModuleName
| |
| |---module-info.java
| |
| |---PackageName
| |
| |---Main.java
|
|---resources
|
|---ResourceParentFolder
|
|---ResourceSubFolderA
| |
| |---Resource1A.png
| |---Resource2A.png
| |---Resource3A.png
|
|---ResourceSubFolderB
|
|---Resource1B.png
|---Resource2B.png
|---Resource3B.png
I have a shell script that compiles the code, and then runs that code.
javac \
--module-source-path="src/main/java" \
--module=ModuleName \
-d classes
java \
--module-path="classes;src/main/resources" \
--module=ModuleName/PackageName.Main
I also have a shell script that turns my compiled code into a modular jar, and then runs that jar.
jar \
--verbose \
--create \
--file run/executable/jar/ProjectName.jar \
--main-class PackageName.Main \
-C classes/ModuleName . \
-C src/main/resources .
java \
--module-path="run/executable/jar" \
--module=ModuleName/PackageName.Main
In my main method, I have a call to java.lang.module.ModuleReader
, specifically to its list() method, that allows me to traverse my module and its contents.
I am able to see the contents of my ResourceParentFolder
if I take my jar file and try to run it, but the call to list()
only returns the .class
files when I am only running my compiled code. Is this because my module is misconfigured? Or is this simply unsupported functionality?
Again, ModuleReader.list()
returns a recursive list of the contents of my source code and my resource folder when run as a jar, but it only returns the source code when run as compiled code. How do I get the compiled code to also populate the ModuleReader.list()
? Or is that just not supported functionality unless it is in a jar or something?
And to be clear, I am well aware that there are a million and one other ways to fetch a resource. But I want to know if it is possible to do it the way I requested above. And if not, then why?
EDIT -- detailing some of my failed attempts.
I tried copying the src/main/resources
directory into classes
, the location of my module before it turns into a jar. Unfortunately, nothing got picked up by it.
I also tried to do --patch-modules
, but that also failed, but with an error.
error: no source files
Here is the command that I used.
javac --patch-module ModuleName=src/main/resources
Your attempt to add src/main/resources
to the module path won't work as you expect. Simply adding directories to the module-path does not make their contents part of your module. You need to either:
Have the classes and the resources in the same location, whether that's a directory or a JAR file. This makes the resources part of the module. And note this is why your JAR file works as expected, because you've packaged the classes and the resources in the same JAR file.
This approach is essentially how it's "supposed to be done".
Use --patch-module
at run-time. Note this approach does not require moving the resource files anywhere.
You would additionally use this argument at compile-time if you were trying to opens
a resource-only package; otherwise, a warning is emitted saying the package doesn't exist. Though if I recall correctly, you'll get an error at run-time in this scenario (where an opens
package is not inherently part of the module) even with a --patch-module
argument.
All that said, I do recommend using a build tool (e.g., Maven, Gradle, etc.) for non-trivial Java projects. They will typically handle all this for you.
Here is an example of both approaches discussed above. Note all commands were executed in the project directory on Windows 10 using PowerShell Core 7.2.13 and Java 20.0.1.
While the output below lists resource directories as well as actual resources, that is not guaranteed. In fact, if I'm not mistaken, those same directories will not be listed when the module is packaged in a run-time image (via jlink
/ jpackage
).
The source code is the same in both approaches.
module-info
module app {}
sample.Main
package sample;
public class Main {
public static void main(String[] args) throws Exception {
var module = Main.class.getModule();
var reference = module.getLayer() // ModuleLayer
.configuration() // Configuration
.findModule(module.getName()) // Optional<ResolvedModule>
.orElseThrow() // ResolvedModule
.reference(); // ModuleReference
try (var reader = reference.open()) {
reader.list().forEach(System.out::println);
}
}
}
Directory structure
<PROJECT-DIR>
|
\---src
\---main
+---java
| \---app
| | module-info.java
| |
| \---sample
| Main.java
|
\---resources
\---app
\---foo
res.txt
Note I added an app
"module directory" under src/main/resources
simply to mirror what was done under src/main/java
(which is needed if you want to use --module-source-path
and --module
with javac
). This is not necessary, though not having it would require changing the commands used below slightly (just changes to paths).
Commands
& javac --module-source-path src\main\java --module app -d build\classes
& robocopy src\main\resources build\classes /S > NUL
& java --module-path build\classes --module app/sample.Main
Output
foo/
foo/res.txt
module-info.class
sample/
sample/Main.class
As you can see, the resource foo/res.txt
(and even the directory foo/
) was listed.
Directory structure (after running commands)
<PROJECT-DIR>
|
+---build
| \---classes
| \---app
| | module-info.class
| |
| +---foo
| | res.txt
| |
| \---sample
| Main.class
|
\---src
\---main
+---java
| \---app
| | module-info.java
| |
| \---sample
| Main.java
|
\---resources
\---app
\---foo
res.txt
--patch-module
Commands
& javac --module-source-path src\main\java --module app -d build\classes
& java --module-path build\classes --patch-module app=src\main\resources\app --module app/sample.Main
Output
module-info.class
sample/
sample/Main.class
foo/
foo/res.txt
Again, you can see the resource foo/res.txt
(and the directory foo/
) was listed.
Directory structure (after running commands)
<PROJECT-DIR>
|
+---build
| \---classes
| \---app
| | module-info.class
| |
| \---sample
| Main.class
|
\---src
\---main
+---java
| \---app
| | module-info.java
| |
| \---sample
| Main.java
|
\---resources
\---app
\---foo
res.txt