4

I have a JavaFX application that works as expected. I need to use Apache POI to read and write excel files. The following are the steps I have taken:

  1. Added the required dependency

    implementation 'org.apache.poi:poi-ooxml:5.2.3'

  2. Added the module to module-info.java

    requires org.apache.poi.ooxml;

  3. Tried to use the library within a function:

@FXML
private void downloadTemplate() {
    XSSFWorkbook workbook = new XSSFWorkbook();
}

All this is fine with no issues. However when I try to run the application, I get the following two errors (interchanging)

> Task :Start.main() FAILED
Error occurred during initialization of boot layer
java.lang.module.FindException: Module SparseBitSet not found, required by org.apache.poi.ooxml

and

> Task :Start.main() FAILED
Error occurred during initialization of boot layer
java.lang.module.FindException: Module commons.math3 not found, required by org.apache.poi.ooxml

I can however, clearly see both libraries under 'external libraries' Expanded libraries view

I am using IntelliJ Community Edition 2022.1.2 and running the project using Java 17.0.1. Any help would be highly appreciated.

Andrej Istomin
  • 2,527
  • 2
  • 15
  • 22
Joseph Sang
  • 310
  • 2
  • 9
  • 2
    Are you using [Gradle](https://gradle.org/) or [Maven](https://maven.apache.org/) or similar? Do you have a [module-info.java](https://www.oracle.com/corporate/features/understanding-java-9-modules.html) file? if you are running your app from IntelliJ, look at the command that IntelliJ executes in order to run your app. – Abra Oct 07 '22 at 11:33
  • 1
    Please post _listings_, not _pictures_ of listings unless [relevant](https://meta.stackoverflow.com/q/285551/230513). When you say _interchanging_, do you mean _repeating_, as if trying to resolve circular dependencies? – trashgod Oct 07 '22 at 12:41
  • 1
    Consider prodding the maintainer of SparseBitSet to merge the months old pull request that may fix this issue: https://github.com/brettwooldridge/SparseBitSet/pull/22 – swpalmer Oct 07 '22 at 18:45
  • 1
    Apache maintainers could fix their part too: https://issues.apache.org/jira/projects/MATH/issues/MATH-1486 – swpalmer Oct 07 '22 at 18:54

2 Answers2

6

SparseBitSet is an automatic module, it has no module-info of its own (probably commons-math3 is as well), and is without an Automatic-Module-Name entry in its manifest.

Gradle puts libraries without a module-info.class or an Automatic-Module-Name in their manifest on the class path, not the module path, so they won't be treated as modules, and the module finder won't find them.

You can:

  1. hack the gradle build to allow the modules to be found. (I don't use Gradle so I have no specific advice on how to do that other than referring to the documentation).
  2. Hack the library jar which you want to be treated as a module to include a module-info.class or an Automatic-Module-Name in its manifest.
  3. Or, switch to maven, which automatically places automatic modules on the module path.
    • The easiest way to do this, IMO, is to create a new JavaFX project in Idea, then add the required dependencies as maven dependencies and add your code.
  4. Or, as swpalmer suggests in the comments, request that library maintainers update their codebase to make their libraries modular.

And, when you run your app, make sure all jars are on the module path, not the class path.

Or, make your app non-modular by removing the module-info.java from it, then manually place the JavaFX modules on the module-path and add them with the --add-modules switch.

FAQ

Are you SURE that automatic modules are put on the class path by Gradle?

From the Gradle documentation section Building Modules for the Java Module System:

To tell the Java compiler that a Jar is a module, as opposed to a traditional Java library, Gradle needs to place it on the so called module path. It is an alternative to the classpath, which is the traditional way to tell the compiler about compiled dependencies. Gradle will automatically put a Jar of your dependencies on the module path, instead of the classpath, if these three things are true:

  • java.modularity.inferModulePath is not turned off

  • We are actually building a module (as opposed to a traditional library) which we expressed by adding the module-info.java file. (Another option is to add the Automatic-Module-Name Jar manifest attribute as described further down.)

  • The Jar our module depends on is itself a module, which Gradles decides based on the presence of a module-info.class — the compiled version of the module descriptor — in the Jar. (Or, alternatively, the presence of an Automatic-Module-Name attribute the Jar manifest)

It is the third point that is key. Java can treat a library with no module-info.class and no Automatic-Module-Name in the Jar manifest as an automatic module if it is on the module path. However, Gradle will by default, only place libraries which fulfill one of those two conditions on the module path.

Slaw
  • 37,820
  • 8
  • 53
  • 80
jewelsea
  • 150,031
  • 14
  • 366
  • 406
  • Are you SURE that automatic modules are put on the class path by Gradle? The documentation only mentions this for jars that have NO module information, not even the ```Automatic-Module-Name``` in the jar manifest. https://docs.gradle.org/current/userguide/java_library_plugin.html#using_libraries_that_are_not_modules – swpalmer Oct 07 '22 at 18:37
  • @swpalmer I clarified (corrected) the answer based on your comment. – jewelsea Oct 07 '22 at 20:18
3

Using jewelsea's answer above, I have been able to solve the problem. I am posting the answer here to help anyone else who encounters the problem in future.

So, the overall problem is, as said in the answer above, both SparseBitSet and commons-math3 are automatic modules with no module-info of their own. The solution that worked for me was to convert them into the modules expected by the project. Here are the steps I took:

  1. Use a gradle plugin 'extra-java-module-info'. The github page didn't show how to import it to a normal gradle file so here it is:

    plugins {
           id 'org.gradlex.extra-java-module-info' version '1.0'
    }
    
  2. Note the names that your application expects for the modules. In my case, from the error messages thrown, they were 'SparseBitSet' and 'commons-math3'

  3. Locate the said libraries on the sidebar under 'external libraries' and note the 'jar' file names. In my case, they were 'commons-math3-3.6.1.jar' and 'SparseBitSet-1.2.jar'.See this image

  4. Add a section 'extraJavaModuleInfo' to your gradle files and use the parameters as follows: module('jar file name', 'name expected by your project', 'jar version'), as shown in the blue rectangle in the image above.

    extraJavaModuleInfo {
        failOnMissingModuleInfo.set(false)
        module('commons-math3-3.6.1.jar', 'commons.math3', '3.6.1')
        module('SparseBitSet-1.2.jar', 'SparseBitSet', '1.2')
    }
    
    

That's it. Try to sync and run your project. Thanks jewelsea.

Community
  • 1
  • 1
Joseph Sang
  • 310
  • 2
  • 9
  • If you continue getting errors from other non-module libraries, add `failOnMissingModuleInfo.set(false)` to `extraJavaModuleInfo` – Mark Woon Jun 19 '23 at 01:47