Integrating Java Modules with Maven and Gradle: A Complete Guide

Illustration for Integrating Java Modules with Maven and Gradle: A Complete Guide
By Last updated:

A frequent challenge developers encounter when adopting the Java Platform Module System (JPMS) is integrating it with popular build tools like Maven and Gradle. Many teams assume that simply adding a module-info.java file will make everything modular, but then builds break with errors like “module not found” or “package not visible.”

This integration problem matters deeply in real-world applications. Enterprises running multi-module systems or migrating from legacy monoliths need reliable build pipelines. Without proper Maven or Gradle configuration, modular projects can’t be compiled, tested, or packaged consistently across environments.

Think of modules as office departments and build tools as the HR system. The HR system must know how each department interacts, who reports where, and how to package the entire company directory. Misconfiguration leads to chaos, just as poor integration between modules and build tools leads to broken builds.


Maven with Java Modules

Key Configuration

Maven requires explicit configuration for modules because its default classpath-based behavior does not align perfectly with JPMS.

pom.xml Example

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>modular-app</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
  </properties>

  <build>
    <plugins>
      <!-- Compiler Plugin -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.11.0</version>
        <configuration>
          <release>17</release>
          <compilerArgs>
            <arg>--module-path</arg>
            <arg>${project.build.outputDirectory}</arg>
          </compilerArgs>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

This setup ensures the compiler uses the module path instead of the classpath.


Testing Modular Code

Use maven-surefire-plugin with argLine to configure the module path:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>3.0.0</version>
  <configuration>
    <argLine>--module-path ${project.build.outputDirectory}</argLine>
  </configuration>
</plugin>

Gradle with Java Modules

Basic Configuration

Gradle added early support for JPMS but requires explicit configuration.

build.gradle Example

plugins {
    id 'java'
}

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

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

Running Modular Applications

tasks.named('run') {
    doFirst {
        jvmArgs += [
            '--module-path', sourceSets.main.runtimeClasspath.asPath,
            '--module', 'com.example.app/com.example.app.Main'
        ]
    }
}

Best Practices & Pitfalls

Best Practices

  • Always define module-info.java explicitly
  • Use toolchains to ensure consistent JDK versions in CI/CD
  • Test modular builds locally before pushing to CI pipelines
  • Keep dependencies updated for modular compatibility

Pitfalls

  • Relying on automatic modules for production
  • Mixing classpath and module path without care
  • Forgetting to configure test plugins (common cause of “module not found” in tests)

What's New in Java Versions?

  • Java 5–8 → N/A (Modules introduced in Java 9)
  • Java 9 → JPMS introduced; build tool support immature
  • Java 11 → Improved Maven/Gradle plugins for JPMS
  • Java 17 → Stable integration with build tools
  • Java 21 → No significant updates across Java versions for this feature

Real-World Analogy

Integrating JPMS with Maven/Gradle is like assembling IKEA furniture. The pieces (modules) are well-designed, but unless you follow the manual (build tool configuration), the final product wobbles. Proper integration ensures everything fits securely.


Summary & Key Takeaways

  • Maven and Gradle require explicit configuration for JPMS
  • Use --module-path and test plugin arguments for smooth builds
  • Avoid automatic modules in production
  • Keep dependencies modularized for best results
  • Consistency in CI/CD is key to stable modular builds

FAQ: Maven and Gradle with JPMS

1. What is the difference between the classpath and module path?
Classpath loads everything without boundaries, module path enforces modular visibility.

2. Why do I get “module not found” in Maven builds?
Because the compiler isn’t configured with the module path correctly.

3. How do I handle “package not visible” errors?
Export the package in module-info.java or adjust module dependencies.

4. Can Gradle run modular applications directly?
Yes, but you must configure jvmArgs with --module-path.

5. Do Maven/Gradle support automatic modules?
Yes, but avoid them in production for reliability.

6. How does JPMS improve security compared to classpath?
By preventing accidental or malicious use of internal APIs.

7. When should I use jlink vs jmod?
jmod for packaging modules, jlink for creating runtime images.

8. Can I migrate a legacy project incrementally?
Yes, modularize gradually and use automatic modules as a bridge.

9. How do I handle third-party non-modular libraries?
Place them on the classpath or rely on automatic modules temporarily.

10. Do frameworks like Spring and Hibernate work with modules?
Yes, though configurations may need adjustments.