Skip to content

User guide

Martin Desruisseaux edited this page Mar 31, 2025 · 13 revisions

This page gives an introduction to the Maven compiler plugin 4.0. For differences compared to the previous compiler plugin, see migration from Maven 3 to Maven 4.

Declaring source files

The recommended way to declare source directories is with <source> elements. Note that these sources are declared in the <build> element and therefore can apply to all plugins. If no source is declared, the default values relevant to the compiler plugin are as below:

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

If a <sources> element is declared, its content will replace the above default values. Therefore, the above defaults may need to be copied if the developer wants to add sources instead of replacing them.

Multiple source directories

The <source> element can be repeated as many times as desired for the same language and scope. Optionally, an enabled flag allows to include or exclude the whole directory according a property value. The following example adds the extension directory only if the value of the include.extension property is set to true. For brevity, this example uses the default values of the <scope> and <lang> elements when applicable.

<build>
  <sources>
    <source>
      <directory>src/main/java</directory>
    </source>
    <source>
      <directory>src/extension/java</directory>
      <enabled>${include.extension}</enabled>
    </source>
    <source>
      <scope>test</scope>
      <directory>src/test/java</directory>
    </source>
  </sources>
<build>

Multi-releases project

The <source> element can be repeated many times with different Java releases. The lowest release value is given to the --release compiler option for the base classes, and the source files in all directories associated to that version are compiled together. Then, the sources of all other releases are compiled in separated javac executions, one execution for each release, in the order of increasing release values. For each new execution, the output directories of all previous executions are added to the class-path with highest releases first, and the output is written in the META-INF/versions/${release}/ sub-directory. Example:

<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>

Note: For targeting Java 8, the version number shall be 8, not 1.8.

Include/exclude filters

For each source directory, the list of source files to compile can be filtered. If <include> elements are specified, only the files that match at least one include filters may be compiled. If no <include> element is specified, then the default filter is glob:**/*.java. Next, if <exclude> elements are specified, all included elements matching at least one exclude filter become excluded.

The filtering implementation uses java.nio.file.PathMatcher. The syntax is described in the Javadoc of standard Java. Various syntaxes are possible, including glob and regex. If no syntax is specified, Maven defaults to the glob syntax where / is the path separator regardless the platform (including Windows), * matches any filename inside a directory and ** matches any number of directories (see above-cited Javadoc for more detailed explanation). All paths to be matched are relative to the path specified in the <directory> element. Example:

<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 project

The recommended way to build a project using the Java Module System is to declare the module name(s) together with the sources, as in the example below. The name declared inside the <module> element must match the name declared inside the module-info.java file of that module. More than one module can be declared if desired. It means that for Java modular projects, there is no longer a one-to-one relationship between a Maven project or subproject and a Java module, unless the developer chooses to restrict herself to exactly one Java module per Maven subproject. Note, however, that there is still a one-to-one relationship between Java modules and Maven artifacts (the JAR files identified by Maven coordinates). See the maven-jar-plugin for more information. (TODO: maven-jar-plugin has not yet been updated for Java module support.)

<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>
    <source>
      <module>my.product.bar</module>
      <directory>src/java/my.product.bar/main</directory>
    </source>
    <source>
      <module>my.product.bar</module>
      <directory>src/java/my.product.bar/test</directory>
      <scope>test</scope>
    </source>
  </sources>
</build>

While it is a common practice to have a sub-directory of the same name as the module, this is not mandatory. It is also a common practice to place the main and test sub-directories after the java sub-directory instead of before it (in order to group them per module), but this is not mandatory neither. It is also possible to specify multiple source directories for the same module.

Compiler output

For a modular project, the compiler writes the class files of each module in a directory of the same name as the module. For example, the classes of the my.product.foo module will be written in the target/classes/my.product.foo/ directory rather than directly in target/classes/. This is the standard javac behavior, not a Maven particularity. Technically, the behavior of the Maven compiler plugin is straightforward: if a <module> element is present, the plugin declares the source directories using the --module-source-path compiler option, which implies the above-cited new directories. Otherwise, the plugin declares the source directories with the --source-path option. The plugin does nothing more.

If that new output directory is not desired, the Maven 3 way to do a modular project is still supported: setup the Maven project has if it was non-modular (without <module> element), but keep adding a module-info.java file in the sources. However, migration to a fully modular project is recommended when applicable.

Compilation of tests

The maven compiler plugin automatically adds --patch-module options for the <source> elements having a test scope. It also adds --add-reads options for dependencies having the test or test-only scope. In many cases, this is sufficient and there is no need for to configure the plugin with additional compiler arguments.

Class-path versus module-path

By default, each dependency of a modular project is placed on the module-path if the dependency contains a module-info.class file or an Automatic-Module-Name entry in the META-INF/MANIFEST.MF file, otherwise the dependency is placed on the class-path. This heuristic rule can be overridden by specifying explicitly the type of the dependency. For example, the following snippet forces the placement of a dependency on the module-path even if that dependency has no module-info or manifest entry:

<dependencies>
  <dependency>
    <groupId>my.dependency</groupId>
    <artifactId>foo</artifactId>
    <type>modular-jar</type>
    <version>1.0</version>
  </dependency>
</dependencies>

If the version is managed by a <dependencyManagement> section, the managed dependency must contains the same <type> element, otherwise Maven will consider that this is not the same artifact. For the reverse operation (force placement on the class-path instead of the module-path even if the dependency is modular), replace modular-jar type by classpath-jar.

Troubleshooting

If the compilation failed, the Maven compiler plugin writes the options that it used in a javac.args or javac-test.args file (for compilation of main code and test code respectively) in the target directory. The compilation can be executed on the command-line as below. Note that the paths to source files inside javac.args are relative to the project directory. Therefore, that command must be executed in the same directory as the pom.xml file, or in a directory containing a copy or a branch of that project.

javac @target/javac.args

By default, javac.args is not written if the compilation succeed. Users can force the plugin to write that file in the following ways:

  • Execute mvn with the --verbose option (more exactly: enable logging messages at the debug level).
  • Or provide the <verbose>true</verbose> option in the plugin configuration.
Clone this wiki locally