Gradle doesn't add modules to module-path during compile

I started a new project, which is modularized.
It contains 4 dependencies, two of which is also modularized.

compile group: 'org.controlsfx', name: 'controlsfx', version: '9.0.0'
compile group: 'com.jfoenix', name: 'jfoenix', version: '9.0.4'
compile group: 'org.jfxtras', name: 'jfxtras-controls', version: '9.0-r1'
compile group: 'com.google.protobuf', name: 'protobuf-java', version: '3.5.1'
testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.2.0'
testRuntime group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version:'5.2.0'

My module-info.java

requires javafx.controls;
requires javafx.fxml;
requires javafx.web;
requires javafx.graphics;

/* Named modules */
requires jfxtras.controls;
requires com.jfoenix;

/* Automatic modules */
requires controlsfx;
requires protobuf.java;

Gradle will not compile as it complains of missing modules for all of these 4 dependencies.

So adding these to build.gradle solves that:

compileJava {
    inputs.property("moduleName", moduleName)
    doFirst {
        options.compilerArgs = [
            '--module-path', classpath.asPath,
        ]
        classpath = files()
    }
}

compileTestJava {
    inputs.property("moduleName", moduleName)
    doFirst {
        options.compilerArgs = [
            '--module-path', classpath.asPath,
            '--add-modules', 'org.junit.jupiter.api',
            '--add-reads', "$moduleName=org.junit.jupiter.api",
            '--patch-module', "$moduleName=" + files(sourceSets.test.java.srcDirs).asPath,
        ]
        classpath = files()
    }
}

As taken from the tutorial:

https://guides.gradle.org/building-java-9-modules/#modify_the_code_compilejava_code_task_to_produce_a_module

However it seems unnecessary boilerplate code. I moved from Maven to Gradle because I liked the Groovy coding, and how much smaller my build.gradle was compared to pom.xml. My pom.xml was ~900 lines and my equivalent build.gradle only 350 lines.

This works out of the box with Maven without any extra configuration messing with the modulepath. So I expected it to work seamlessly with Gradle as well.

It seems Gradle support for Java 9 is not complete. Now that Java 11 is soon here this should have been ironed out. We should expect same behaviour for modulepath as we have to classpath. If a module add all dependencies to modulepath, else add all dependencies to classpath.

This works fine with the application plugin without having to specify module-path. I can execute gradle run and the application starts up.
I am able to run without the run configuration in the Guide
https://guides.gradle.org/building-java-9-modules/#modify_the_code_run_code_task_to_use_modules


The guide states that gradle support for Java 9 modules are lacking:

While Gradle does not yet support building Java 9 modules as a first-class feature of the Java plugins, an experimental plugin is available to allow you to experiment with Java 9 modules on your projects.


I found this piece of information:
https://github.com/elastic/elasticsearch/issues/28984

“Maven provides the jar to JDK ModuleFinder
If the ModuleFinder considers the Jar as a module, Maven adds the Jar to --module-path
If the ModuleFinder does not consider the Jar as a module (i.e. elasticsearch), Maven adds it to -classpath”

“Gradle doesn’t make any kind of jigsaw module autodetection.
By default, even in Java 9, Gradle will put all dependencies into the classpath.
This is confirmed by the tutorial that expects us to populate the --module-path and override -classpath manually.”

5 Likes

Java Module Madness is here now that JavaFX 11 has been released.
I have started using it as dependencies.

Already before JavaFX 11 I had to set
‘–module-path’, classpath.asPath
for compileJava, compileTestJava, javadoc and test.

I didn’t however need to do it for application plugin run task. Until now.
With the JavaFX modules I have now also needed to --add-module for each.

This Gradle configuration has grown. In this regard it no longer has an advantage over Maven, which doesn’t need to set all these flags.

I really hope Gradle is working on making Java 9 module support better, and without all this boilerplate configuration.

compileJava {
    inputs.property("moduleName", moduleName)
    doFirst {
        options.compilerArgs = [
            '--module-path', classpath.asPath,
            '--add-modules', 'javafx.controls',
            '--add-modules', 'javafx.fxml',
            '--add-modules', 'javafx.web',
            '--add-modules', 'javafx.graphics',
            '--add-modules', 'javafx.media'
        ]
        classpath = files()
    }
}

compileTestJava {
    inputs.property("moduleName", moduleName)
    doFirst {
        options.compilerArgs = [
            '--module-path', classpath.asPath,
            '--add-modules', 'javafx.controls',
            '--add-modules', 'javafx.fxml',
            '--add-modules', 'javafx.web',
            '--add-modules', 'javafx.graphics',
            '--add-modules', 'javafx.media',
            '--add-modules', 'org.junit.jupiter.api',
            '--add-reads', "$moduleName=org.junit.jupiter.api",
            '--patch-module', "$moduleName=" + files(sourceSets.test.java.srcDirs).asPath,
        ]
        classpath = files()
    }
}

run {
    doFirst {
        jvmArgs = [
                '--module-path', classpath.asPath,
                '--add-modules', 'javafx.controls',
                '--add-modules', 'javafx.fxml',
                '--add-modules', 'javafx.web',
                '--add-modules', 'javafx.graphics',
                '--add-modules', 'javafx.media'
        ]
    }
}

javadoc {
    inputs.property("moduleName", moduleName)
    doFirst {
        exclude "**/module-info.java"
        options.addStringOption('-module-path', classpath.asPath)
        options.addStringOption('-add-modules', 'javafx.controls')
        options.addStringOption('-add-modules', 'javafx.fxml')
        options.addStringOption('-add-modules', 'javafx.web')
        options.addStringOption('-add-modules', 'javafx.graphics')
        options.addStringOption('-add-modules', 'javafx.media')
        options.addStringOption('-class-path', "")
        options.addBooleanOption('html5', true)
        options.addBooleanOption('verbose', true)
    }
}

test {
    useJUnitPlatform()

    doFirst {
        jvmArgs = [
            '--module-path', classpath.asPath,
            '--add-modules', 'javafx.controls',
            '--add-modules', 'javafx.fxml',
            '--add-modules', 'javafx.web',
            '--add-modules', 'javafx.graphics',
            '--add-modules', 'javafx.media'
        ]
    }
}
3 Likes

I would think in the presence of a module-info.java file Gradle would by default fill out the module path. But as you (and I) have discovered it does not. Doesn’t make much sense.

1 Like

They haven’t had the time to refactor the code to support modules. It’s a complex undertaking. I’ve modified their experimental plugin to better support modules. I will probably release a new version of it soon that will only support Gradle 5.6+. I can’t, however, remember the state of the published plugin. It’s available at:

https://plugins.gradle.org/plugin/rgoldberg.experimental-jigsaw

If you try it, please let me know if it works for you, or if you experience any issues.

Related issue:

If someone knows whether this can be fixed by some configuration or whether it is a bug, please let me know.

It seems that this boilerplate has now been encapsulated in a property by Gradle 6.4: https://docs.gradle.org/6.4/samples/sample_java_modules_multi_project.html
By just using this code construct (example copied from the link):

    plugins.withType(JavaPlugin).configureEach {
        java {
            modularity.inferModulePath = true
        }
    }

“Worked for me”
Cheers

1 Like

I am using modularity.inferModulePath = true with Gradle 6.4 but I still can’t get it to compile without this:

java {
    modularity.inferModulePath = true

    tasks.withType(JavaCompile) {
        doFirst {
            options.compilerArgs = [
                    '--module-path', classpath.asPath,
            ]
            classpath = files()
        }
    }
}

And I can’t get the tests to run without this:

tasks.withType(Test).configureEach {
        useJUnitPlatform()
        // The following jvm args fix a runtime problem where some pre-java 9 modules are not being added to module path
        doFirst {
            jvmArgs = [
                    '--module-path', classpath.asPath,
                    '--add-modules', 'ALL-MODULE-PATH', // to resolve all modules in the module path to be accessed by gradle test runner
                    '--add-exports', 'java.base/jdk.internal.misc=ALL-UNNAMED',
                    '--add-exports', 'org.junit.platform.commons/org.junit.platform.commons.logging=ALL-UNNAMED',
                    '--add-exports', 'org.junit.platform.commons/org.junit.platform.commons.util=ALL-UNNAMED',
                    '--add-reads', "$moduleName=ALL-UNNAMED",
                    '--patch-module', "$moduleName=ALL-UNNAMED=" + files(sourceSets.test.java.outputDir).asPath,
            ]
            classpath = files()
        }
    }

My full build.gradle is as follows:

allprojects {
    apply plugin: 'java'
    apply plugin: 'idea'
}

subprojects {

    repositories {
        mavenCentral()
        jcenter()
    }

    plugins.withType(JavaPlugin).configureEach {
        java {
            modularity.inferModulePath = true

            tasks.withType(JavaCompile) {
                doFirst {
                    options.compilerArgs = [
                            '--module-path', classpath.asPath,
                    ]
                    classpath = files()
                }
            }
        }

        sourceSets {
            integrationTest
        }

        configurations {
            all {
                //Stops java 9+ jdeps split package errors
                exclude group: 'javax.servlet', module: 'javax.servlet-api'
                exclude group: 'com.google.code.findbugs', module: 'jsr305'
                exclude group: 'com.vaadin.external.google', module: 'android-json'
            }

            integrationTestImplementation.extendsFrom implementation
            integrationTestRuntimeOnly.extendsFrom runtimeOnly
        }

        dependencies {
            testImplementation 'org.springframework.boot:spring-boot-starter-test'
            testImplementation 'org.mockito:mockito-junit-jupiter:3.3.3'
         //   testImplementation 'org.junit.jupiter:junit-jupiter:5.4.2'

            integrationTestImplementation project(path)
            integrationTestImplementation 'org.springframework.boot:spring-boot-starter-test'
            integrationTestImplementation 'org.hamcrest:hamcrest-core:2.2'
            integrationTestImplementation 'com.h2database:h2:1.4.178'

      def integrationTestJarTask = tasks.register(sourceSets.integrationTest.jarTaskName, Jar) {
            archiveClassifier = 'integration-tests'
            from sourceSets.integrationTest.output
        }
        def integrationTestTask = tasks.register('integrationTest', Test) {
            description = 'Runs integration tests.'
            group = 'verification'

            testClassesDirs = sourceSets.integrationTest.output.classesDirs
            // Make sure we run the 'Jar' containing the tests (and not just the 'classes' folder) so that test resources are also part of the test module
            classpath = configurations[sourceSets.integrationTest.runtimeClasspathConfigurationName] + files(integrationTestJarTask)
            shouldRunAfter('test')
        }

        tasks.named('check') { dependsOn(integrationTestTask) }
    }

    tasks.withType(Test).configureEach {
        useJUnitPlatform()
        // The following jvm args fix a runtime problem where some pre-java 9 modules are not being added to module path
        doFirst {
            jvmArgs = [
                    '--module-path', classpath.asPath,
                    '--add-modules', 'ALL-MODULE-PATH', // to resolve all modules in the module path to be accessed by gradle test runner
                    '--add-exports', 'java.base/jdk.internal.misc=ALL-UNNAMED',
                    '--add-exports', 'org.junit.platform.commons/org.junit.platform.commons.logging=ALL-UNNAMED',
                    '--add-exports', 'org.junit.platform.commons/org.junit.platform.commons.util=ALL-UNNAMED',
                    '--add-reads', "$moduleName=ALL-UNNAMED",
                    '--patch-module', "$moduleName=ALL-UNNAMED=" + files(sourceSets.test.java.outputDir).asPath,
            ]
            classpath = files()
        }
    }
}

Any ideas? I am using SpringBoot version 2.2.4.RELEASE

I came across this thread as I couldn’t get my very simple module example to work with Gradle 7.4 without fixing the module path manually per doFirst as described above.

However, it then turned out I had made a mistake: I had put the module-info.java in the wrong “root folder”. If your layout is src/main/java/org/and_so_on, then the module file for things from org.and_so_on should be at src/main/java/module-info.java (and not src/main/java/org/module-info.java).

After moving my module file up that one level, it worked using just modularity.inferModulePath = true

1 Like