-
Notifications
You must be signed in to change notification settings - Fork 0
User guide
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.
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.
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.
These defaults are main
and java
respectively.
<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>
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>
If "glob:" or other syntax was specified as a prefix of a pattern, the pattern is used as-is without any change. If no syntax is specified, then the pattern is modified as below before to be used as a glob syntax:
- The platform-specific separator (
\
on Windows) is replaced by/
. - Trailing
/
is completed as/**
. - The
**
wildcard is interpreted as "0 or more directories" instead of "1 or more directories". This is implemented by adding variants of the pattern with**
progressively removed. - Bracket characters
[
,]
,{
and}
are escaped. - On Unix, the escape character
\
is itself escaped (on Windows, it was the path separator converted to/
).
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
.
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.
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 output directory.
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.
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
.
By default, the maven compiler plugin automatically adds the following options when compiling the tests:
-
--patch-module
options for each<source>
elements having atest
scope. -
--add-modules
option with a value set to all dependencies having thetest
ortest-only
scope. -
--add-reads
options (repeated for each Java module to patch) for all dependencies having thetest
ortest-only
scope.
In many cases, this is sufficient and there is no need to configure the plugin with additional compiler arguments.
However, sometime there is a need for more control.
In particular, there is often a need to export or open more packages for whitebox testing.
While it can be done by specifying options such as --add-exports
in the <compilerArgs>
element of the plugin configuration,
this is tedious, error prone and need to be repeated in other plugins such as Surefire.
The Maven compiler plugin offers a more convenient mechanism, described below.
This is a variation of the deprecated practice consisting in overwriting the
module-info.java
of the main code with another module-info.java
defined in the test directory.
Instead, the module-info.java
file formerly defined in the test
should be replaced by a module-info-patch.maven
file in the same directory.
The later is Maven-specific: the content of module-info-patch.maven
looks like module-info.java
,
but is not standard Java, hence the .maven
file suffix.
The principles are:
- Everything that a developer would like to change in a
module-info.java
file for testing purposes is declared inmodule-info-patch.maven
. - Everything that is not in
module-info.java
is not inmodule-info-patch.maven
neither. In particular, everything that specify paths to JAR files stay in thepom.xml
file. - All keywords inside the
patch-module
block of that file map directly to Java compiler or Java launcher options.
The syntax is:
- The same styles of comment as Java (
/*
…*/
and//
) are accepted. - The first tokens, after comments, shall be
patch-module
followed by the name of the module to patch. - All keywords inside
patch-module
are Java compiler or Java launcher options without the leading--
characters. - Each option value ends at the
;
character, which is mandatory.
The accepted keywords are add-modules
, limit-modules
, add-reads
, add-exports
and add-opens
.
Note that they are options where the values are package or module names, not paths to source or binary files.
Options with path values should be handled in the <sources>
and <dependencies>
elements of the POM instead.
Below is an example of a module-info-patch.maven
file content
for modifying the module-info
of a module named my.product.foo
:
/*
* The same comments as in Java are allowed.
*/
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 org.junit.jupiter.api, // Frequently used dependency for tests.
my.product.test.fixture; // Put here any other dependency needed for tests.
add-exports my.product.foo.internal // Name of a package which is normally not exported.
to org.junit.jupiter.api, // Any module that need access to above package for testing.
my.product.test.fixture; // Can export to many modules, as a coma-separated list.
add-exports my.product.foo.mock // Another package to export. It may be a package defined in the tests.
to my.product.biz; // Another module of this project which may want to reuse test classes.
}
The following values have special meanings:
-
SUBPROJECT-MODULES
: all other modules in the current Maven (sub)project.- This is Maven-specific, not a standard value recognized by Java tools.
- Allowed in:
add-exports
.
-
TEST-MODULE-PATH
: all dependencies having a test scope.- This is Maven-specific, not a standard value recognized by Java tools.
- Allowed in:
add-modules
,add-reads
andadd-exports
.
-
ALL-MODULE-PATH
: everything on the module path, regardless if test or main.- This is a standard value accepted by the Java compiler.
- Allowed in:
add-modules
.
-
ALL-UNNAMED
: all non-modular dependencies.- This is a standard value accepted by the Java compiler.
- Allowed in:
add-exports
.
If no module-info-patch.maven
file is present, the default behavior of the Maven compiler plugin
is as if a file was present with the following content:
patch-module <module name> {
add-modules TEST-MODULE-PATH;
add-reads TEST-MODULE-PATH;
}
If a module-info-patch.maven
file is present, then it should contain the above
add-modules
and add-reads
lines if the default behavior is desired.
However, while add-modules TEST-MODULE-PATH
is usually fine,
it is recommended to put values more specific than TEST-MODULE-PATH
for the add-reads
option.
These values can be easily determined by compiling the tests without add-reads
option.
The compiler error messages are explicit about which reads are missing.
All options declared in a module-info-patch.maven
file apply only
to the module declared after the patch-module
token,
except the --add-modules
and --limit-modules
options.
These two options apply to all modules in a Maven (sub)project.
Therefore, it is not necessary to repeat add-modules TEST-MODULE-PATH
in all modules:
declaring that particular option in only one module of a Maven (sub)project is sufficient.
If the --add-modules
or --limit-modules
options are declared in many
module-info-patch.maven
files of a Maven (sub)project,
the effective value will be the union of the values declared in each file.
The content of all module-info-patch.maven
files of a Maven sub(project) are compiled in
a META-INF/maven/module-info-patch.args
file in the output directory of the test classes.
This file differs from javac-test.args
(described in the next section below) in two ways:
- It contains only the options declared in
module-info-patch.maven
, not the other options such as--module-path
and--patch-module
. - It contains the dependencies with
test-runtime
scope and not the dependencies withtest-only
scope, whilejavac-test.args
contains the opposite.
This file is intended for use with the java
launcher in plugins such as Surefire.
Note that there is a single module-info-patch.args
output file even if a
multi-modular project contains many module-info-patch.maven
input files.
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.
This section contains a few common errors that may occur during the execution of mvn compile
,
together with their fixes.
Ensure that the dependency is declared in the pom.xml
with a compile
or provided
scope.
Check that the module name is correct with the following command:
jar --describe-module --file <path/to/the/dependency.jar>
The module name will be one of the first lines.
If that line also contains "automatic", then org.foo
is a filename-based automodule.
In such case, ensure that the <dependency>
declaration in the pom.xml
file contains <type>modular-jar</type>
.
If a <dependencyManagement>
section exists, then the dependency in that section needs the same <type>
declaration.
This section contains a few common errors that may occur during the execution of mvn test-compile
,
together with their fixes.
Ensure that the dependency is declared in the pom.xml
with a compile
, provided
, test
or test-only
scope.
If the Maven (sub)project contains some module-info-patch.maven
files,
ensure that at least one of them contains an add-modules
statement like below
(replace my.product.foo
by the actual module name):
module-info-patch my.product.foo {
add-modules TEST-MODULE-PATH;
}
Alternatively, TEST-MODULE-PATH
can be replaced by an explicit enumeration of the missing modules.
It is not necessary to repeat that declaration in all files of a Maven (sub)project,
as the effective value of add-modules
is the union of the values found in all these files.
If the Maven (sub)project has no module-info-patch.maven
file,
then there is no need to add one since the above declaration is the default.
If the error continues to happen, then if a module-info-patch.maven
file contains an add-reads
option,
check that the option value contains the module. If this is the case, see "module not found: org.foo" in the
"Common errors during compilation of main code" section.
Check the next line, which should look like
"package org.foo.bar is declared in module org.foo, which does not export it".
Add a module-info-patch.maven
file in the test source files of that org.foo
module, if not already present.
Then add an add-exports
statement like below in that file,
where org.biz
can usually be inferred from the path to the file where the error occurred:
module-info-patch org.foo {
add-exports org.foo.bar to org.biz;
}