2

Question looks easy, and probably has an easy solution, but I didn't find one.

Given module A that needs module B, which in turn needs module C.

Module A should not need to know about module C. I well guess this is one major point about using modules.

How to do that? (I have found many oversimplified examples for declaring A to require B, but B never required anything else.)


The question is general, but in my example case, I have a project "Tracker" (corresponding to module A) and a project "Poppy" (corresponding to module B) which in turn depends on modules from javafx.

"Poppy" pops up a popup, and when on its own, works fine. "Tracker" has no use of javafx, except indirectly via "Poppy", but Poppy might be changed to use Swing instead of javafx, or other graphics libraries.

module-info are

module Tracker {
    requires transitive Poppy;
    opens tracker to pop;
}

and

module Poppy {
    requires transitive javafx.graphics;
    requires transitive javafx.controls;
    requires transitive javafx.base;

    opens pop to javafx.graphics, javafx.fxml;

    exports pop;
    
}

Where pop is the package name for the main class pop.Poppy.

Tracker calls Poppy.popup()

with

public static void popup() {
    Scene scene;
    Stage st = new Stage();
    VBox r2 = createRoot(null);
    scene = new Scene(r2);
    st.setScene(scene);
    st.show();
}

The call (developing with eclipse) result in java.lang.module.FindException: Module javafx.controls not found, required by Poppy.

Other tries resulted in complaints about other javafx.thingy not found.


Eclipse does not show me any (compile) errors. Just to double-check, in Poppy I changed the method name popup() to xpopup() and got the expected error for Tracker that there is no Poppy.popup().

So Tracker is aware of Poppy (but not of javafx, or anything Poppy cares to depend on, and rightfully should not be).


Meanwhile, I suspected the imports as a possible cause, and moved them to a different class in a different package that is not exported, now the pop package is clean of any direct references to javafx but calls a static method of a different class in a different package.

No success.

Has anyone out there ever done that before, nesting three modules A, B and C such that A doesn't need to have to declare all the stuff needed by B, C and whatever C and its dependencies might be using?


What am I missing?


The answers were guessing right, it is a XYProblem, or, as I like to put it: I tried to fix the healthy leg instead of the broken one.

17
  • 3
    Although it's not related to the error, why do you declare Poppy as requires transitive, and not just plain requires? Because by declaring it transitive, your telling the module system that Tracker will also require on all modules that Poppy requires. That seems contradictory with your claim that Tracker doesn't need to know that. Commented Sep 16 at 10:53
  • 1
    Tracker needs Poppy on the module path, and Poppy requires all those other modules, so they need to be there as well. I'm not sure what you're asking. If you really need to decouple them at compile time, you'd need a poppy-api module that Tracker depends on, and then use - for example - the service loader mechanism to locate and load the implementation (e.g. poppy-javafx) at runtime. (I'm handwaving this, and my experience with the module system is a bit basic; there might be better ways.) Commented Sep 16 at 11:05
  • 1
    In this regard, there’s a huge difference between what Java requires and what Eclipse requires. Eclipse really likes to traverse project dependencies recursively and brag about something missing or invalid that’s totally irrelevant to the code you’re just trying to compile. This behavior regarding project dependencies is not even depending on whether you use modules or not. Commented Sep 16 at 12:02
  • 2
    This is not about Tracker (assuming you removed transitive) but about the fact that the initialization of a module layer requires all dependencies, including indirect ones, to be present right at the start. Which removes the problem of failing at a later time, when required classes are missing. So if you want to run Tracker which depends on Poppy which depends on JavaFX, then, of course, JavaFX must be present. How else is it supposed to work? Commented Sep 16 at 12:40
  • 5
    Your questions say that you get the error message “Module javafx.controls not found, required by Poppy”. So, the problem is that the module javafx.controls is not present, in this particular run configuration. It might be present in the run configuration used to run Poppy stand-alone, but the problem is about the other environment, used to run Tracker. Otherwise, it would imply you’re claiming that the error message is factually wrong. That would be an entirely new discussion. Commented Sep 17 at 9:42

2 Answers 2

5

The answer to your general question is that you'd have:

module A {
  requires B;
}

And:

module B {
  requires C;
}

That sets up the modules so that module A only knows about module B and nothing about module C. I think you understand that solution, so I believe this to be somewhat of an XY Problem. You're focusing on how to define the module descriptors, but your real problem would appear to be that the --module-path is not being configured correctly at run-time.

When running your application, the command should look something like:

java --module-path <path> --module A/com.example.Main

Where <path> "contains" all needed modules. With the above, that means module A, B, and C need to be on that path. This does not mean that module A "knows about" module C. But module B requires module C, and so module C needs to be loadable. Otherwise you'll get an exception saying module C cannot be found. Again, not because module A "thinks it needs module C itself", rather that module A requires module B and module B requires module C.

This is not any different for applications that don't make use of Java Platform Module System (JPMS) modules. If project A depends on project B, and project B depends on project C, then all three projects would need to be on the classpath at run-time, regardless of project A knowing about project C. If project C was not on the classpath then the one difference is that you'd get a ClassNotFoundException when a type from project C is used for the first time, whenever that may be, instead of getting a guaranteed FindException right at the start.


There's an additional implicit question you seem to have, and that is how module B might swap out module C for module D. That depends on when you want to be able to swap the modules.

  • At compile/build time: This is as "simple" as rewriting module B to use module D instead of module C. You'd only be changing implementation details of module B, and so module A would not have to change at all.

  • At execution time: This is not possible if you have requires C in module B's descriptor. However, you could define a service and make use of the ServiceLoader mechanism. Module B would uses that service and you'd have other modules provides that service. Something like:

    module B {
      exports com.example.spi;
    
      uses com.example.spi.GuiService;
    }
    
    module CProvider {
      requires B;
      requires C;
    
      provides com.example.spi.GuiService with
          com.example.cprovider.CGuiService;
    }
    
    module DProvider {
      requires B;
      requires D;
    
      provides com.example.spi.GuiService with
          com.example.dprovider.DGuiService;
    }
    

    Here you'd put either module CProvider or module DProvider on the --module-path when executing your application. If you put module CProvider on the path then module C will also need to be on the path. If you instead put module DProvider on the path then module D will also need to be on the path. Regardless, both modules A and B would still also need to be on the path.

    Of course, you could put both CProvider and DProvider on the module-path. Then the logic to choose which provider implementation to use would be in the code (i.e., the choice is now made during the application's execution instead of when launching the application).

Sign up to request clarification or add additional context in comments.

Comments

1

The issue isn’t your imports or the transitive keyword — it’s that the JavaFX modules themselves must still be present on the runtime module path.

Declaring

requires transitive javafx.controls;

in Poppy only means: “any module that requires Poppy can also use JavaFX types at compile time.”
It does not put javafx.controls on the module path at runtime. That’s why you get:

java.lang.module.FindException: Module javafx.controls not found

Fix:

  1. Put the JavaFX jars on the module path when running. Example:
java --module-path lib/javafx:out \
     --add-modules Tracker \
     tracker.Main

(where lib/javafx contains the JavaFX jars).

  1. In Poppy’s module-info.java, just require JavaFX normally:
module Poppy {
    requires javafx.controls;
    requires javafx.graphics;
    requires javafx.base;

    opens pop to javafx.graphics, javafx.fxml;
    exports pop;
}
  1. In Tracker’s module-info.java:
module Tracker {
    requires Poppy;
    opens tracker to Poppy;
}

Now:

  • Tracker can call Poppy.popup() without knowing about JavaFX.

  • Poppy handles its own dependencies.

  • Only JavaFX needs to be on the runtime module path.

Note:

  • If Tracker never touches JavaFX, don’t make Poppy’s requires transitive. That keeps the dependency graph clean.

With this setup you get exactly what you want: A → B → C works, A does not need to know about C, but C must still be available at runtime.

10 Comments

So, at least the call to Trackerhas to know what modules Poppy is using?
@GyroGearloose Yes, but conceptually this doesn't mean that Tracker "knows about" the modules used by Poppy. It's more that the developer, or whoever is responsible for deployment, needs to know all the modules being used by the entire application. Modules are mostly a closed system, so this shouldn't be too difficult. The only open part of the module system is potential services (uses / provides), where a decision needs to be made about which providers to include at run-time (might be all of them, depending on the design of the service).
Meanwhile I tried the command line from eclipse run configuration, and it doesn't work, but telling eclipse to pack a runable jar it can be started (but has other errors that point to a programming error on my part. So it was, as both answers sugested, not a problem with module-info but with eclipse, probably clicking some wrong option when configuring things. Thanks a lot.
The requires transitive javafx.controls; does not only mean: “any module that requires Poppy can also use JavaFX types at compile time.”, it also means that those modules are allowed to use JavaFX types at runtime, which otherwise would be highly problematic as most uses at compile-time lead to a use at runtime.
It is correct that requires transitive lets Module A see JavaFX types from Module B at compile time and makes it legal for Module A to use them according to the module system. What’s misleading is your wording, that it might sound like JavaFX will automatically be available at runtime — it won’t. You still need JavaFX on the runtime module path, otherwise you get java.lang.module.FindException.
“are allowed to use JavaFX types” does nowhere support the idea that those types become magically available. It says exactly the same thing you said for compile-time, just for the runtime. Of course, you must provide JavaFX on the runtime module path, the same way you must provide JavaFX on the compile-time module path.
@GyroGearloose "but telling eclipse to pack a runable jar it can be started" -- Be careful with this. If you're launching this JAR file with -jar <file> or -cp <path> <main-class> then the code is ending up on the class-path, not the module-path. This means all code is ending up in the unnamed module and none of the module-info descriptors have any effect. Also, if Eclipse is creating a so-called "fat" JAR file, then keep in mind the JAR File Specification only allows one module per JAR file. Does your JAR work with java -p <path> -m <main-module>[/<main-class>]?
I get warnings, about duplicate module-info.class and similar issues. My guess is that eclipse "export" is lacking behind. They have problems since java 1.8, very suddenly, added new syntax and even more since the module-feature was added to java. Looks to me like the dev-teams for java and those for eclipse are not on the best terms with each other. As I can get it to work, one way or the other, I'll wait for eclipse to improve.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.