3

Recently I've found out that if a package inside a module contains public interfaces or classes they are being exported automatically and hence unintentionally visible outside the module in case when recipient code is not modularized.

Structure of the project is as follows

parent

   sub-donor                      (java-module)
       my/project/first
           Cowboy.java
       module-info.java

   sub-recipient                  (has no module-info)
       my/project/somewhere
           Main.java 

module-info.java

module my.donor {
  // nothing is exported here
}

Cowboy.java

public class Cowboy {    
  public String say() { return "eee-ha"; }
}

Main.java

import my.project.first.Cowboy;

public class Main {
  public static void main(String[] args) {
    Cowboy g = new Cowboy();

    System.out.println(g.say());
  }
}

The class from my.project.first package is supposed to be used in my.donor module only and be invisible outside the module.

Unexpectedly it is visible outside the module as if there were an exports my.project.first; line, so the class Main can be compiled.

The way to restrict visibility is to make sub-recipient a java module, adding module-info.java, then everything inside of sub-donor becomes hidden as expected. Of course, for any other non-modular project every public class in my.donor module remains visible.

Please see a minimal working example on GitHub.

I am curious whether it is a bug or a conscious design. What's the purpose of this approach?

diziaq
  • 6,881
  • 16
  • 54
  • 96
  • 1
    "Unexpectedly they are visible" How did you determine this? Curious as whether you have an `opens` directive in your `module.info`, and came to this conclusion at runtime. – Michael May 19 '20 at 10:45
  • @Michael, @Naman I've updated the question. The problem can be reproduced when recipient module has no `module-info.java` file. An example on github is attached. – diziaq May 20 '20 at 05:43
  • Please can you edit your question to include the ***full*** command you ran that produces the behavior you reported? TIA. – deduper Sep 20 '20 at 18:22
  • @deduper Full command is just `mvn compile` on the parent project. The problem is visibility of internal packages of `sub-donor` module from `sub-recipient` in compile time. – diziaq Oct 07 '20 at 04:51

1 Answers1

2

TL;DR — Maven's yer problem. Compiling with javac directly works as expected.


The long-winded version

…Full command is just mvn compile on the parent project…

And therein lies your problem. Maven has a surprising feature where it will only treat a subproject as a JPMS module IFF all of the subprojects have module descriptors.

So because only your sub-donor Maven module has the JPMS module descriptor, both of your subprojects are getting compiled on the classpath…

$ mvn -e -X compile
…
[INFO] Changes detected - recompiling the module!
[DEBUG] Classpath:
[DEBUG]  sample-so-question-61888059-dev/sub-recipient/target/classes
[DEBUG]  sample-so-question-61888059-dev/sub-donor/target/classes
…
[DEBUG] Command line options:
[DEBUG] -d sample-so-question-61888059-dev/sub-recipient/target/classes -classpath sample-so-question-61888059-dev/sub-recipient/target/classes;sample-so-question-61888059-dev/sub-donor/target/classes;…
…

And because both are on the classpath, the module descriptor of the sub-donor subproject is rendered null and void.

If you take matters into your own hands though and run javac directly (assuming you've compiled the sub-donor project beforehand)…

javac --add-modules my.donor --module-path sample-so-question-61888059-dev/sub-donor/target/classes --class-path sample-so-question-61888059-dev/sub-recipient/target/classes -d sample-so-question-61888059-dev/sub-recipient/target/classes sample-so-question-61888059-dev/sub-recipient/src/main/java/my/somewhere/*.java

Then javac does indeed balk as consciously designed…

sample-so-question-61888059-dev/sub-recipient/src/main/java/my/somewhere/Main.java:3: error: package my.project.first is not visible
import my.project.first.Cowboy;
                 ^
  (package my.project.first is declared in module my.donor, which does not export it)
1 error
deduper
  • 1,944
  • 9
  • 22