1

I'm in the process of trying to migrate a large code base from Java 8 to modular (JPMS) Java 11, and I'm encountering significant pain and finding consistent advice on project structure and how to use module-info files for actual production projects is hard.

The project in question follows the conventional gradle structure for source and test files:

src/main/java/org/abc/...
src/test/java/org/abc/...

I have a module-info.java file in src/main/java/module-info.java, is this the right location? The quick-start for modular java and this seems to contradict this; however other resources do it the way I did.

When trying to run a unit test that looks like this:

@RunWith(MockitoJUnitRunner.class)
public class ABCTest {
    @Mock
    public SomeClass mock;

    ...
}

I get:

Unable to make field public SomeClass mock accessible: module org.abc does not "exports org.abc" 
to module org.mockito

which indicates that I need to add exports org.abc to org.mockito in my module-info.java file but this seems clunky and verbose having to do this for all sub packages in my test tree which aren't even part of the module, and it seems like the wrong place to do it since the tests shouldn't be exported with the release jar.

The seemingly most up to date guidance I could find suggests adding a module-info.[test|java] file to my test tree. What is the difference between these two files (.java and .test)? I couldn't find any hits on Google explaining this. Also I'm supposedly going to have to copy content from the main/java/module-info.java to the test/java/module-info.test and keep them in sync? This seems tedious.

I also understand that gradle doesn't have native support for modular java yet and one must rely on plugins for this, what are the "defacto" plugins that one should use?

I'm confused by the information available, it seems contradicting, very low level or not applicable when using gradle. Could some one please provide an example project with conventional source layout using mockito, junit, gradle and modular java (version 9+, preferably 11)?

Emily L.
  • 5,673
  • 2
  • 40
  • 60
  • Seems the situation is yet unclear. But there are already articles like https://dzone.com/articles/building-java-6-8-libraries-for-jpms-in-gradle. Related: https://stackoverflow.com/questions/59117333/how-can-i-set-the-modulemainclass-attribute-of-a-jpms-module-using-gradle – tkruse Dec 08 '19 at 16:20
  • Even if you solve that one access issue, you will then run up against an *`InvalidModuleDescriptorException`*; caused by the *`xpp3:xpp3_min:1.1.4c`* artifact. JPMS determines that it is an invalid module because it contains a Service Provider configuration file that is malformed. It is one of a few often-encountered artifacts that are the bane of any JPMS project unfortunate enough to have them end up on the module path. The only sane solution is to refactor your application to use a different XML Pull Parser. *StAX* for example. Which is built-in to the JDK within the *`java.xml`* module. – deduper Dec 13 '19 at 23:16

2 Answers2

1

Gradle doesn't have built-in out-of-the-box support for JPMS, as you've found out.

Gradle's JPMS policy can be summed up by this comment from one of the Gradle Core Devs...

„...There's no short-term plan to work on jigsaw support, so please don't hold your breath...“

I've developed a plugin that JPMS-enables Gradle and Eclipse though.

Here is a simple test project that is implemented with Gradle, JPMS, JavaFX 13, JUnit 4 and Mockito. The mrJar JPMS development-enabling plugin is what ties it all together.

You could run the :test task or the :run task of that test project from the command line with just raw Gradle. Or follow these steps to use the test project in Eclipse...

  1. Import the project into Eclipse

  2. Execute the :eclipse task in Eclipse's Gradle Tasks view

    • this is what triggers the Eclipse Modulefication feature of mrJar
  3. Open the project's properties dialog

    • go to the Java Build Path properties tab
    • notice the notification in the title bar (Not all modules could be found. Click Apply to synchronize)
    • click the Apply button to synchronize the module path
  4. Execute the :test task

    • or alternatively, do Run As Gradle Test from the Package Explorer context menu
    • or alternatively again, executing the :check task is another option that has the same Eclipse Modulefication effect as :eclipse, :test and Run As Gradle Test

The recording might help clarify the steps you need to follow to run the unit tests and the JavaFX application in Eclipse.

Hopefully you will find the attached test project pretty straightforward. But if you get stuck, there are more detailed usage instructions and screen recordings in this Gradle Communnity Forum thread.

I'm also happy to engage with you in that forum thread and clarify anything that might not be obvious in either the video or any of the steps.


Also, here's a github repository with a fully JPMS-enabled copy of your project @EmilyL...

JPMS Modules Made Easy

deduper
  • 1,944
  • 9
  • 22
1

My other answer was a response to your „Could some one please provide an example project with conventional source layout using mockito, junit, gradle and modular java (version 9+, preferably 11)?“ request.

But you probably also want an answer to the question in your title.

TL;DR: Both of the directory structures you've come across are both equally legal. But for single module projects, the conventional src/main/java is all you need.

The long answer

The less-often-seen src/com.foo.mymodule/io/bar/apackage structure is a way to organize your modules nice and neatly in a folder that is the same name as the module. Then the packages of the module go within that flat module name directory as usual.

It is not just a stylistic choice however. There are some practical, technical advantages to organizing modules that less-typical way.

The JPMS --module-source-path command line option makes it possible to compile several different modules in one shot. So you could have multiple modules organized like so...

.
.
└── src
    ├── com.foo.mymodule
    │   ├── module-info.java
    │   └── io
    │       └── bar
    │           └── apackage
    │               ├── *.java
    │               └── ...
    ├── net.other.module
    │   ├── module-info.java
    │   └── org
    │       ├── example
    │       │   └── yadda
    │       │       ├── *.java
    │       │       └── ...
    │       └── another
    │           └── pkg
    │               ├── *.java
    │               └── ...
    ├── mr.jar
    │   ├── module-info.java
    │   └── com
    │       └── lingocoder
    │           └── plugin
    │               ├── *.java
    │               └── ...
    ├── and.so.forth
    └── ...            

Then javac -d mods --module-source-path src ... can compile all of the code of all of the modules nested under src.

It will mirror the directory structures under src while storing the generated class files in the destination directory (mods in my example).

For example, all generated class files for the net.other.module module would be stored in the mods/net.other.module directory; with the package hierarchy mirrored under that.

deduper
  • 1,944
  • 9
  • 22
  • How would this work with tests? Where would you put them? I believe some authors make special test modules, does that make sense, or is there a more 'lightweight' way? – Eric Dec 04 '22 at 18:44