Automatic modules (jigsaw) and gradle

I’m using gradle 7.6 & Java 17.

I’ve been looking at converting a large gradle project to use java modules and hit one glitch.

Some of the libraries I am using as dependencies have no module-info, nor any name in the manifest. IntelliJ & Java use a computed name for the module – for example ‘kafka.clients’ for the kafka client jar (maven dependency), or the first one I hit:

jar --file=/Users/jonesn/Downloads/plexus-utils-3.5.0.jar --describe-module
No module descriptor found. Derived automatic module.

plexus.utils@3.5.0 automatic
requires java.base mandated
contains org.codehaus.plexus.util
contains org.codehaus.plexus.util.cli
contains org.codehaus.plexus.util.cli.shell
contains org.codehaus.plexus.util.dag
contains org.codehaus.plexus.util.introspection
contains org.codehaus.plexus.util.io
contains org.codehaus.plexus.util.reflection
contains org.codehaus.plexus.util.xml
contains org.codehaus.plexus.util.xml.pull

(albeit that is more easy to replace with base java!)

However in the gradle docs ie The Java Library Plugin this fails the third condition, meaning the library is not put on the modulepath

This then means the compile fails, since the module cannot be found by gradle. (despite the fact the definition was added by IntelliJ)

Any suggestions as to the best way around this?

similar to Specify a dependency should be on the module path · Issue #17609 · gradle/gradle · GitHub

Is there any reason not to follow the logic Gradle is employing, putting non-modular jars without explicit automatic module name on the classpath?

If so, you can either just configure the module path explicitly to contain all runtime classpath jars, or you can use for example the plugin Gradle - Plugin: de.jjohannes.extra-java-module-info that can transform non-modular jars into modular jars.

I had expected/hoped gradle would make use of the automatic module derivation ie

jar --file=/Users/jonesn/Downloads/kafka-clients-3.3.1.jar --describe-module
No module descriptor found. Derived automatic module.

kafka.clients@3.3.1 automatic
requires java.base mandated
provides org.apache.kafka.common.config.provider.ConfigProvider with org.apache.kafka.common.config.provider.DirectoryConfigProvider org.apache.kafka.common.config.provider.FileConfigProvider
contains org.apache.kafka.clients
contains org.apache.kafka.clients.admin

so in this case created a module ‘kafka-clients’

This is also what’s stated in the java 9 jar file specification JAR File Specification

… and confusing in gradle since including a module name in the manifest is enough to warrant different hehaviour in terms of module vs classpath ?

Thanks to the link for the plugin though, will take a look - am still new to modules… !

Yes, the JAR file Specification specifies what should happen if you put a non-modular jar file on the module path.
But that does not make it a good idea.
Non-modular jar files are often not JPMS ready.
They might have split-packages.
They might use things that cannot work if they are on the module path.
They might have a name with an illegal identifier that makes it impossible to derive a module name from the jar name.
The module name changes if you rename the jar file.
The module name changes if later the author of that library decides to make it a module and so the module name used in consumer module info files is wrong and cannot be used with the newer version of the library, …
There is just so much shenenigans and non-determinism if you put non-modular jars on the module path, that the Gradle guys wisely decided to not put non-modular jars on the module path, but at most such that declare an automatic module name in the manifest which should be a sign that the library author at least thought about it and at least made it JPMS compatible.

But as always Gradle just provides sane defaults and gives you the flexibility to easily do whatever you want, so if you really want to put those files on the module path for your project, then just configure your build however you like it. :slight_smile:

Thanks for the reply. I was able to get this working (at least as a prototype) by adding a small number of module definitions using the plugin mentioned.

I was confused by the difference in default behaviour though between gradle and plain java in terms of automatic modules. I wonder if this needs calling our more clearly in the docs (maybe I missed it) as it caught me out.

I was confused by the difference in default behaviour though between gradle and plain java in terms of automatic modules.

I wouldn’t actually say there is a difference.
If you put all jars on the module path with Gradle, it will behave the same as if you put all jars on the module path with plain Java as Gradle just forwards this to “plain Java” as Gradle is a layer on top, not something in parallel. :slight_smile:

The difference is, that if you do it with “plain Java”, you tell Java to put all jars on the module path.
And if you use Gradle, it is more intelligent and only puts those jars on the module path that at least have an automatic module name.

I wonder if this needs calling our more clearly in the docs (maybe I missed it) as it caught me out.

Quote from The Java Library Plugin

A third case are traditional libraries that provide no module information at all — for example commons-cli:commons-cli:1.4 . Gradle puts such libraries on the classpath instead of the module path. The classpath is then treated as one module (the so called unnamed module) by Java.