Skip to content

Incompatible changes

Martin Desruisseaux edited this page Apr 1, 2025 · 6 revisions

This page describes behaviors of the Maven compiler plugin 4 that changed in incompatibles way compared to versions 3. Some projects building successfully with version 3 of maven-compiler-plugin may fail with version 4 because of those changes. This page describes corrections that can be applied to projects for fixing those build failures.

Cannot use both --release and --source

As a general rule, this new Maven plugin does not modify the compiler options specified in the configuration. This policy may break projects that specify the <release> option together with <source> and <target>. The previous version of the plugin applied some heuristic rules for deciding which options to pass to the Java compiler. The new version passes all options verbatim, which may result in the following message from the javac compiler:

option --source cannot be used together with --release

Correction if the build fails

Removes the <source> and <target> configuration parameters. They may be specified indirectly through the maven.compiler.source and maven.compiler.target properties. If those configuration parameters or properties are inherited from a super-POM that cannot be modified, clear the <source> and <target> parameters as below:

<project>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source/>
          <target/>
          <release>17</release>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

Modular project in the Maven 3 way

Problems may occur if a Java modular project is defined in the Maven 3 way (i.e., like a non-modular project with only the addition of a module-info.java file), if the module name is a single name without . separator (for example, foo or bar but not foo.bar) and that name is identical to a package name. It is because in such case, the hack implemented in the Maven compiler plugin for Maven 3 compatibility become confused about whether a directory named foo represents the module or the package.

Correction if the build fails

Define the Java modular project in the Maven 4 way. Keep in mind that it will change the layout of the target/classes directory. Example:

<build>
  <sources>
    <source>
      <module>foo</module>
      <directory>src/java/main</directory>
    </source>
  </sources>
</build>

Use of dependencies with automatic names

If a dependency does not declare explicitly whether the JAR file should be placed on the class-path or on the module-path, the Maven plugin uses heuristic rules for making its own decision. Those rules are not obvious, and the selected option is not always appropriate for the project. The results may sometime be different between this version and the previous version of maven-compiler-plugin. Some JAR files may placed on the class-path when they were previously on the module-path, or conversely.

By default, a dependency is placed on --module-path if the project is itself modularized (i.e., contains a module-info.java) and one of the following conditions is true:

  • The JAR contains a module-info.class entry.
  • The JAR contains a META-INF/MANIFEST.MF entry with an Automatic-Module-Name attribute.

In all other cases, the dependency is placed on --class-path. This default behavior may be adjusted in future Maven plugin versions depending on experience.

Correction if the build fails

Developers are strongly encouraged to specify explicitly where they want their non-modular JAR files to appear in a modular project. It can be done by setting the type of a dependency to modular-jar or classpath-jar. Example:

<project>
  <dependencies>
    <dependency>
      <groupId>org.codehaus.plexus</groupId>
      <artifactId>plexus-utils</artifactId>
      <version>3.0.24</version>
      <type>modular-jar</type>
    </dependency>
  </dependencies>
</project>

Note that specifying the dependency type is usually not needed in other cases (e.g., non-modular project, or dependencies that are already modular). It should not be needed neither for dependencies with the runtime scope. However, it may be a good practice to be explicit anyway for more guarantees.

Guideline for choosing the dependency type

If a non-modular dependency is forced to the modular-jar type, Java will automatically infer a module name from the file name. That name can be shown by the jar --describe-module command. For example, the following command shows the name of above plexus-utils dependency on a Unix system:

jar --describe-module --file ~/.m2/repository/org/codehaus/plexus/plexus-utils/3.0.24/plexus-utils-3.0.24.jar

The command output contains [email protected] automatic. That module name must be declared in the module-info.java file.

module my.module {
    requires plexus.utils;
}

Conversely, if a dependency is forced to the classpath-jar type, then the module-info.java is not modified. Instead, the pom.xml file should have the following configuration (replace my.module by the actual project module name):

<project>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <compilerArgs>
            <arg>--add-reads</arg>
            <arg>my.module=ALL-UNNAMED</arg>
          </compilerArgs>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

jpms.args generation

The META-INF/jpms.args file is no longer generated. The closest match is target/javac.args which is generated if the build fails, or if the --verbose option was provided to the mvn command.