Apache Maven Compiler Plugin

repository·master·Indexed 42 days ago

https://github.com/apache/maven-compiler-plugin

A core Maven plugin used to compile the source code of a project. It supports configuring annotation processing for Maven 3 and 4, managing JDK executables via Maven Toolchains or fork settings, and handling Java Platform Module System (JPMS) requirements including module-info.java for Java 8 and 9+ compatibility. Version 4 introduces javac.args files for debugging compilation arguments and supports upper-case memory suffixes.

Tokens
11.8K
Snippets
28
Records
43
Agent score
54%

What's inside Maven Compiler Plugin

  1. Overview of Maven Compiler Plugin goals

    master

    The Maven Compiler Plugin is used to compile project source files. By default, it uses the JDK javac compiler. The plugin provides two primary goals that are automatically bound to the Maven Lifecycle:

    • compiler:compile: Bound to the compile phase; used to compile main source files.
    • compiler:testCompile: Bound to the test-compile phase; used to compile test source files.
  2. Identify JPMS arguments for runtime configuration

    master

    When working with the Java Platform Module System (JPMS), certain arguments used during compilation (especially for tests) must also be provided at runtime to ensure the application behaves correctly. If your tests compile but fail at runtime due to module visibility or access issues, check the target/javac-test.args file for the following arguments:

    • --upgrade-module-path
    • --add-exports
    • --add-reads
    • --add-modules
    • --limit-modules
    • --patch-module

    Note: These arguments should generally appear in javac-test.args (for tests) rather than javac.args (for main code). If they are required for main code, it may indicate that your module-info.java file is incomplete.

  3. Syntax for module-info-patch.maven

    master

    The module-info-patch.maven file uses a syntax similar to Java, C/C++, or JavaScript.

    Rules:

    • Comments: Use /* ... */ or //.
    • Declaration: The first non-comment token must be patch-module followed by the name of the module being patched.
    • Keywords: Use Java compiler/launcher options without the leading -- (e.g., use add-reads instead of --add-reads).
    • Termination: Every option statement must end with a mandatory semicolon (;).
    • Scope: Most options apply only to the module declared in the patch-module header. However, add-modules and limit-modules apply to all modules in a multi-module project.
  4. Understand Multi-Release JARs (JEP-238)

    master

    A Multi-Release JAR allows you to include Java version-dependent classes within a single JAR file. The Java runtime automatically selects the best matching version of a class based on the current runtime.

    To enable this behavior, the JAR must contain a MANIFEST.MF file with the attribute Multi-Release: true. Without this flag, the runtime will only use the base classes located in the root of the JAR and ignore version-specific classes located in META-INF/versions/.

    .
    ├─ A.class
    ├─ B.class
    ├─ C.class
    ├─ D.class
    └─ META-INF
       ├─ MANIFEST.MF { Multi-Release: true }
       └─ versions
          ├─ 9
          │  ├─ A.class
          │  └─ B.class
          └─ 10
             ├─ A.class
             └─ C.class
  5. Perform Black Box vs White Box testing in modular projects

    master

    Black Box Testing

    Tests executed without access to the internal code (package-private classes/methods or non-exported packages). Using the module source hierarchy, you can create a dedicated test module (e.g., org.foo.bar.test) that interacts with your main module (org.foo.bar) as an external client would.

    White Box Testing

    Tests that have access to internal classes. In Maven, any <source> element with the test scope automatically performs white box testing for the module declared in its <module> element. Access to package-private members is granted if the test code resides in the same package as the code under test. Access to non-exported modules is implicit for the module the tests belong to.

  6. How module-info-patch.maven is translated to compiler arguments

    master

    The Maven compiler plugin translates the .maven syntax into standard Java tool arguments. You can inspect the resulting arguments in target/javac.args, target/javac-test.args (if a build fails or --verbose is used), or target/test-classes/META-INF/maven/module-info-patch.args for test execution.

    Translation Logic:

    • add-modules mod1, mod2; $\rightarrow$ --add-modules mod1,mod2 (spaces are removed).
    • limit-modules mod1, mod2; $\rightarrow$ --limit-modules mod1,mod2 (spaces are removed).
    • add-reads mod1, mod2; $\rightarrow$ --add-reads [patched-module]=mod1,mod2.
    • add-exports pkg to mod1, mod2; $\rightarrow$ --add-exports [patched-module]/pkg=mod1,mod2.
    • add-opens pkg to mod1, mod2; $\rightarrow$ --add-opens [patched-module]/pkg=mod1,mod2 (applied to runtime execution).
  7. Compare Include/Exclude filters: Maven 3 vs Maven 4

    master

    There is a functional difference between how filters are applied in Maven 3 and Maven 4:

    1. Maven 3 (Plugin-specific): Filters defined in the maven-compiler-plugin configuration apply to all source directories but are only respected by the compiler plugin.
    2. Maven 4 (Directory-specific): Filters defined within a <source> element apply only to that specific directory. These filters are intended to be used by all plugins that have migrated to the Maven 4 API (e.g., not just the compiler plugin).

    Use the Maven 4 <sources> approach to ensure filters are applied precisely to the intended directories and are available to the broader Maven ecosystem.

  8. Configure modular projects in Maven 4 with package hierarchy

    master

    Maven 4 supports the Maven 3 directory layout, but using module-info.java in the test directory is deprecated. Instead, you should use a module-info-patch.maven file in the test/java directory.

    Unlike the Maven 3 approach where the test module-info.java replaces the main one, the module-info-patch.maven file completes the main module-info.class by applying additional compiler arguments (like --patch-module, --add-modules, --add-reads, --add-exports, or --add-opens).

    Limitation: Avoid using module names that are single words without a . separator (e.g., foo) if they match a package name, as this can cause ambiguity in the compiler plugin's compatibility layer. Use reverse domain name conventions (e.g., foo.bar).

    src
    ├─ main
    │  └─ java
    │     ├─ module-info.java
    │     └─ org/foo/bar/*.java
    ├─ test
    │  └─ java
    │     ├─ module-info-patch.maven   (optional)
    │     └─ org/foo/bar/*.java
    └─ target
       └─ classes
          └─ org/foo/bar/*.class
  9. Use non-javac compilers in Maven Compiler Plugin 4

    master

    In version 4 of the Maven Compiler Plugin, you can use compilers other than the standard javac (from javax.tools) by providing a compiler implementation that declares itself as a javax.tools.JavaCompiler service.

    To implement this:

    1. Add the compiler implementation as a dependency within the maven-compiler-plugin configuration.
    2. Set the compilerId element to the value returned by the implementation's JavaCompiler.name() method.

    Example using the Eclipse compiler (ecj):

    <project>
      [...]
      <build>
        [...]
        <plugins>
          [...]
          <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>...</version>
            <configuration>
              <compilerId>ecj</compilerId>
            </configuration>
            <dependencies>
              <dependency>
                <groupId>org.eclipse.jdt</groupId>
                <artifactId>ecj</artifactId>
                <version>...</version>
              </dependency>
            </dependencies>
          </plugin>
        </plugins>
      </build>
    </project>
  10. Use module-info-patch.maven for white box testing

    master

    When performing white box testing, you often need compiler options like --patch-module, --add-modules, --add-reads, --add-exports, and --add-opens. Instead of manually managing these in <compilerArgs> in your pom.xml or duplicating your module-info.java in the test directory, you can use a module-info-patch.maven file.

    This file allows you to declare only the changes needed for testing. The Maven compiler plugin then translates these declarations into the appropriate Java compiler or launcher options. This approach is less redundant, more readable, and prevents your test module information from getting out of sync with your main code.

    /* 
     * Create a file named module-info-patch.maven in your module directory 
     */
    patch-module your.module.name {
        add-reads some.dependency;
        add-exports your.package to some.test.module;
    }
  11. Configure modular projects in Maven 4 with module source hierarchy

    master

    The module source hierarchy introduces an additional directory level named after the Java module. This allows a single Maven sub-project to contain multiple Java modules, facilitating easier sharing of test code and resolving compiler warnings.

    For a module named org.foo.bar, the directory structure should include the module name as a parent to main and test directories. The compiled output in target/classes will also include this module name directory level (generated by javac).

    To implement this, define the <sources> in your pom.xml using the <module> tag.

    <build>
      <sources>
        <source>
          <module>org.foo.bar</module>
        </source>
        <source>
          <scope>test</scope>
          <module>org.foo.bar</module>
        </source>
      </sources>
    </build>
  12. Alternative: Use moditect-maven-plugin for Java 8 module-info

    master

    Instead of compiling twice with maven-compiler-plugin, you can use the moditect-maven-plugin to generate a module-info.class file in a single build pass. This allows you to keep your maven.compiler.release property set to a lower version (like 8) for the entire build.

    Use the add-module-info goal to add a module descriptor to your project JAR during the package phase.

    <project>
      <properties>
        <maven.compiler.release>8</maven.compiler.release>
        <project.package.name>com.example.project</project.package.name>
      </properties>
    
      <build>
        <plugin>
          <groupId>org.moditect</groupId>
          <artifactId>moditect-maven-plugin</artifactId>
          <version>1.3.0.Final</version>
          <executions>
            <execution>
              <id>add-module-infos</id>
              <phase>package</phase>
              <goals>
                <goal>add-module-info</goal>
              </goals>
              <configuration>
                <module>
                  <moduleInfo>
                    <name>${project.package.name}</name>
                    <exports>
                      ${project.package.name};
                    </exports>
                  </moduleInfo>
                </module>
              </configuration>
            </execution>
          </executions>
        </plugin>
      </build>
    </project>