Skip to content

Migration

Martin Desruisseaux edited this page Apr 27, 2025 · 12 revisions

This page lists some usage patterns of Maven 3 which have changed in Maven 4.

Declaration of source directories

The Maven 3 elements are deprecated and should be replaced by the new <sources> element introduced in Maven 4.

Maven 3

<build>
  <sourceDirectory>src/main/java</sourceDirectory>
  <testSourceDirectory>src/test/java</testSourceDirectory>
</build>

Maven 4

<build>
  <sources>
    <source>
      <directory>src/main/java</directory>
    </source>
    <source>
      <scope>test</scope>
      <directory>src/test/java</directory>
    </source>
  </sources>
<build>

Note that the declaration of a <sources> element replaces the default values, hence the need to declare the source and test directories together. The new <source> element allows more flexibility such as specifying many directories, the include/exclude filters, the targeted Java release and more.

Declaration of many source directories

The external plugin used in Maven 3 is no longer needed and should be replaced by the build-in <sources> elements.

Maven 3

<build>
  <plugins>
    <plugin>
      <groupId>org.codehaus.mojo</groupId>
      <artifactId>build-helper-maven-plugin</artifactId>
      <executions>
        <execution>
          <id>add-directory</id>
          <goals>
            <goal>add-source</goal>
          </goals>
          <configuration>
            <sources>
              <source>src/extension/java</source>
            </sources>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

Maven 4

<build>
  <sources>
    <source>
      <directory>src/main/java</directory>
    </source>
    <source>
      <directory>src/extension/java</directory>
    </source>
    <source>
      <scope>test</scope>
      <directory>src/test/java</directory>
    </source>
  </sources>
<build>

Reminder: because <sources> replaces the default values, these defaults need to be explicitly specified.

Multi-releases project

The new compiler plugin handles automatically multiple executions of javac with different --release option values, and with automatic adjustments of class-path and output directories for producing a multi-releases project.

Maven 3

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <executions>
        <execution>
          <id>compile-java-17</id>
          <goals>
            <goal>compile</goal>
          </goals>
          <configuration>
            <release>17</release>
          </configuration>
        </execution>
        <execution>
          <id>compile-java-21</id>
          <phase>compile</phase>
          <goals>
            <goal>compile</goal>
          </goals>
          <configuration>
            <release>21</release>
            <compileSourceRoots>
              <compileSourceRoot>${project.basedir}/src/main/java_21</compileSourceRoot>
            </compileSourceRoots>
            <multiReleaseOutput>true</multiReleaseOutput>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

Maven 4

<build>
  <sources>
    <source>
      <directory>src/main/java</directory>
      <targetVersion>17</targetVersion>
    </source>
    <source>
      <directory>src/main/java_21</directory>
      <targetVersion>21</targetVersion>
    </source>
    <source>
      <scope>test</scope>
      <directory>src/test/java</directory>
    </source>
  </sources>
</build>

Include/exclude filters

The Maven 3 way to declare include/exclude filters is still supported, but should be replaced by the <sources> element when applicable. Those two ways are not strictly equivalent:

  • The Maven 4 way specifies filters independently for each source directory. These filters will be applied by all plugins that migrated to the Maven 4 API, not only the compiler plugin.
  • Conversely, the Maven 3 way specifies filters which will be applied only by the compiler plugin. However, these filters apply to all source directories.

Maven 3

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <configuration>
        <excludes>
          <exclude>**/Foo*.java</exclude>
        </excludes>
      </configuration>
    </plugin>
  </plugins>
</build>

Maven 4

<build>
  <sources>
    <source>
      <directory>src/main/java</directory>
      <excludes>
        <exclude>**/Foo*.java</exclude>
      </excludes>
    </source>
    <source>
      <scope>test</scope>
      <directory>src/test/java</directory>
    </source>
  </sources>
</build>

Modular projects

The Maven 3 way to make a modular project is to put a module-info.java file in the root directory of Java source files. Because the compilation and execution of tests usually require an amended version of module information, Maven 3 allows to overwrite that file with another module-info.java file placed in the test source directory. While this approach is still supported in Maven 4 for compatibility reasons, it is deprecated and may no longer be supported in a future version. Developers are strongly encouraged to migrate to the approach described in this section.

Maven 3

The directory layout was as below:

src
├─ main
│  └─ java
│     ├─ module-info.java
│     └─ my/product/*.java
├─ test
│  └─ java
│     ├─ module-info.java
│     └─ my/product/*.java
└─ target
   └─ classes

An alternative to test/java/module-info.java was to declare compiler arguments such as --add-reads in the <testCompilerArgs> element of the plugin configuration.

Maven 4 with package hierarchy

Same directory layout as Maven 3 with only the test/java/module-info.java file removed or replaced by a module-info-patch.maven file in the same directory. This approach can be used when compatibility with Maven 3 is required regarding the source and target directory structures.

src
├─ main
│  └─ java
│     ├─ module-info.java
│     └─ my/product/*.java
├─ test
│  └─ java
│     ├─ module-info-patch.maven   (optional)
│     └─ my/product/*.java
└─ target
   └─ classes
      └─ my/product/*.class

The Maven compiler automatically adds --patch-module, --add-modules and --add-reads arguments for compiling the tests. In many cases (but not always), a <testCompilerArgs> plugin configuration is unnecessary. If more control is needed, a module-info-patch.maven file can be used instead of the module-info.java file which was used to be put in the test/java directory.

Limitation

When using the package hierarchy, problems may occur 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. 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. For avoiding hack and ambiguity, prefer the module source hierarchy described below.

Maven 4 with module source hierarchy

See the user guide for more information. This approach is recommended for new modular projects, (TODO: pending upgrade of other Maven plugins) as it makes the best use of compiler options dedicated to modular projects.

src
├─ java
│  └─ my.product.foo
│     ├─ main
│     │  ├─ module-info.java
│     │  └─ my/product/*.java
│     └─ test
│        ├─ module-info-patch.maven   (optional)
│        └─ my/product/*.java
└─ target
   └─ classes
      └─ my.product.foo
         └─ my/product/*.class

Above layout needs to be declared with the following section in the pom.xml file:

<build>
  <sources>
    <source>
      <module>my.product.foo</module>
      <directory>src/java/my.product.foo/main</directory>
    </source>
    <source>
      <module>my.product.foo</module>
      <directory>src/java/my.product.foo/test</directory>
      <scope>test</scope>
    </source>
  </sources>
</build>

The Maven compiler automatically adds --patch-module and --add-reads arguments for compiling the tests. In many cases (but not always), a <testCompilerArgs> plugin configuration is unnecessary.

Reusing the test classes of another module

For non-modular projects, or for modular projects that do not use the module source hierarchy described in the above section, the Maven 3 dependencies of type test-jar continue to work. For modular projects using module source hierarchy, an easier alternative is possible if all the Java modules that need the shared test classes are in the same Maven (sub)project. In such case, the shared test classes can be declared in any package of any base module (a module required by all other modules that will need the test classes). That package does not need to exist in the main code. It can be, for example, my.product.test. Then, the only thing to do is to export that package to the other modules. It can be done easily with a module-info-patch.maven file having the following content:

patch-module my.product.foo {         // Put here the name of the module to patch.
    add-modules TEST-MODULE-PATH;     // Recommended value in the majority of cases.
    add-reads TEST-MODULE-PATH;

    add-exports my.product.test       // The package that contains the test fixtures.
             to SUBPROJECT-MODULES;   // The other modules which want to use those test fixtures.
}

SUBPROJECT-MODULES is a Maven-specific keyword for exporting to all other Java modules in the Maven (sub)project being compiled. It can be replaced by an explicit list of modules. That's all, no need to deploy or install a test JAR. See the user guide for more information about the module-info-patch.maven file.