ZetCode

Maven Compiler Plugin

last modified November 8, 2026

In this article we show how to configure Java compilation with the Maven Compiler Plugin. The examples use Maven 3.10, Java 25, and version 3.16.0 of the plugin.

The Compiler plugin compiles the sources of a project. Its compile goal is bound to the compile phase and compiles the main sources; its testCompile goal is bound to the test-compile phase and compiles the tests. By default it calls the javac compiler of the JDK that runs Maven through the javax.tools API.

Compiling without configuration

The most important setting is the Java version. If a project does not configure it, the plugin still uses its old defaults: source and target 8. Here is a project that uses records and sealed interfaces but has no compiler configuration.

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>compiler-example</artifactId>
    <version>1.0.0</version>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.16.0</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

The POM only pins the plugin version in pluginManagement.

src/main/java/com/example/App.java
package com.example;

import java.util.List;

public class App {

    sealed interface Shape permits Circle, Square {}

    record Circle(double radius) implements Shape {}

    record Square(double side) implements Shape {}

    static double area(Shape shape) {
        return switch (shape) {
            case Circle(var r) -> Math.PI * r * r;
            case Square(var s) -> s * s;
        };
    }

    public static void main(String[] args) {
        var shapes = List.of(new Circle(1.5), new Square(2));

        for (var shape : shapes) {
            System.out.printf("%s: %.2f%n", shape, area(shape));
        }

        var info = """
                Compiled for Java %d
                Running on Java %s""".formatted(
                Runtime.version().feature(), System.getProperty("java.version"));
        System.out.println(info);
    }
}

The application uses a sealed interface, records, record patterns in a switch expression, and a text block. All of these need a recent Java version.

$ mvn compile
[INFO] Compiling 1 source file with javac [debug target 1.8] to target/classes
[WARNING] source value 8 is obsolete and will be removed in a future release
[ERROR] /home/user/noconfig/src/main/java/com/example/App.java:[9,5] records are not supported in -source 8
[ERROR] BUILD FAILURE
[ERROR] /home/user/noconfig/src/main/java/com/example/App.java:[9,5] records are not supported in -source 8
...

Setting the Java release

The recommended way to set the Java version is the maven.compiler.release property. We add it to the same project.

pom.xml (properties)
<properties>
    <maven.compiler.release>25</maven.compiler.release>
</properties>

The property sets the release parameter of the plugin, which becomes the --release option of javac. The same can be written in the plugin configuration as <release>25</release>. The properties of the form maven.compiler.* are user properties: they can also be set on the command line with -D, as long as the POM does not set the parameter directly in the plugin configuration.

We do not need to set the source encoding. Since Maven 3.10, the project.build.sourceEncoding property defaults to UTF-8, and the plugin's encoding parameter defaults to that property.

$ mvn compile
[INFO] --- compiler:3.16.0:compile (default-compile) @ compiler-example ---
[INFO] Compiling 1 source file with javac [debug release 25] to target/classes
[INFO] BUILD SUCCESS
...
$ java -cp target/classes com.example.App
Circle[radius=1.5]: 7.07
Square[side=2.0]: 4.00
Compiled for Java 25
Running on Java 25.0.3

release versus source and target

Older POMs use the maven.compiler.source and maven.compiler.target properties instead. These only set the language level and the bytecode version; the code is still compiled against the class library of the JDK running Maven. The following project is compiled for Java 21 on JDK 25.

pom.xml (properties)
<properties>
    <maven.compiler.source>21</maven.compiler.source>
    <maven.compiler.target>21</maven.compiler.target>
</properties>

The project sets source and target to 21.

src/main/java/com/example/App.java
package com.example;

public class App {

    public static void main(String[] args) {
        IO.println("Hello from " + System.getProperty("java.version"));
    }
}

The java.lang.IO class was added in Java 25. Since the compiler uses the JDK 25 class library, the code compiles. The class files are marked as Java 21 class files, but they fail on Java 21 with a NoClassDefFoundError. The compiler warns about exactly this problem and recommends --release.

$ mvn compile
[INFO] Compiling 1 source file with javac [debug target 21] to target/classes
[WARNING] location of system modules is not set in conjunction with -source 21
  not setting the location of system modules may lead to class files that cannot run on JDK 21
    --release 21 is recommended instead of -source 21 -target 21 because it sets the location of system modules automatically
[INFO] BUILD SUCCESS
...
$ java -cp target/classes com.example.App
Hello from 25.0.3
$ ~/.sdkman/candidates/java/21.0.2-amzn/bin/java -cp target/classes com.example.App
Exception in thread "main" java.lang.NoClassDefFoundError: java/lang/IO
	at com.example.App.main(App.java:6)
Caused by: java.lang.ClassNotFoundException: java.lang.IO
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
	... 1 more

Recompiling after configuration changes

With the release option, the compiler checks the API as well. Note that the plugin compiles incrementally: it only recompiles when sources or dependencies change. Passing a different option on the command line is not detected, so the first command reports that there is nothing to compile. After clean, the compiler rejects the IO class, which does not exist in the Java 21 API.

$ mvn compile -Dmaven.compiler.release=21
[INFO] Nothing to compile - all classes are up to date.
[INFO] BUILD SUCCESS
...
$ mvn clean compile -Dmaven.compiler.release=21
[INFO] Compiling 1 source file with javac [debug release 21] to target/classes
[ERROR] /home/user/srctarget/src/main/java/com/example/App.java:[6,9] cannot find symbol
  symbol:   variable IO
[ERROR] BUILD FAILURE
[ERROR] /home/user/srctarget/src/main/java/com/example/App.java:[6,9] cannot find symbol
...

Compiler warnings

By default, javac only prints a summary note about deprecated and unchecked code. The compilerArgs parameter passes options to the compiler; -Xlint:all turns on all lint warnings.

pom.xml (compiler plugin)
<plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.16.0</version>
    <configuration>
        <compilerArgs>
            <arg>-Xlint:all</arg>
        </compilerArgs>
    </configuration>
</plugin>

The -Xlint:all option enables all warning categories. Single categories can be disabled with a minus sign, for example -Xlint:all,-serial.

src/main/java/com/example/App.java
package com.example;

import java.util.ArrayList;
import java.util.List;

public class App {

    public static void main(String[] args) {
        long id = Thread.currentThread().getId();
        System.out.println("Thread id: " + id);

        List words = new ArrayList();
        words.add("falcon");
        System.out.println(words);
    }
}

The code calls the deprecated Thread.getId method and uses a raw List type.

The first build prints both warnings and succeeds. The failOnWarning parameter (the maven.compiler.failOnWarning user property) adds the -Werror option, which turns warnings into a build failure. A related parameter, showDeprecation, adds the -deprecation option; it is not needed here, because -Xlint:all includes deprecation warnings.

$ mvn compile
[INFO] Compiling 1 source file with javac [debug release 25] to target/classes
[WARNING] /home/user/warnings/src/main/java/com/example/App.java:[9,41] getId() in java.lang.Thread has been deprecated
[WARNING] /home/user/warnings/src/main/java/com/example/App.java:[12,9] found raw type: java.util.List
[WARNING] /home/user/warnings/src/main/java/com/example/App.java:[12,26] found raw type: java.util.ArrayList
[WARNING] /home/user/warnings/src/main/java/com/example/App.java:[13,18] unchecked call to add(E) as a member of the raw type java.util.List
[INFO] BUILD SUCCESS
...
$ mvn clean compile -Dmaven.compiler.failOnWarning=true
[INFO] Compiling 1 source file with javac [debug release 25] to target/classes
[ERROR] /home/user/warnings/src/main/java/com/example/App.java: warnings found and -Werror specified
[ERROR] BUILD FAILURE
[ERROR] /home/user/warnings/src/main/java/com/example/App.java: warnings found and -Werror specified
...

Main and test sources

The tests are compiled by the testCompile goal, which uses the same configuration as compile. A few parameters have test counterparts: the maven.compiler.testRelease property, for instance, sets a different release for test code.

<properties>
    <maven.compiler.release>17</maven.compiler.release>
    <maven.compiler.testRelease>25</maven.compiler.testRelease>
</properties>

Here the library is compiled for Java 17, while the tests can use Java 25 features. To configure other parameters separately, we override the default-compile or default-testCompile execution, as shown in the Maven build phases article.

Multi-release JAR files

A multi-release JAR (JEP 238) contains different versions of some classes for different Java versions. The base classes are in the root of the JAR; the versioned classes are in META-INF/versions/N. A Java runtime of version N or newer loads the versioned class instead of the base one.

pom.xml (excerpt)
<properties>
    <maven.compiler.release>21</maven.compiler.release>
</properties>

<build>
    <plugins>
        <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.16.0</version>
            <executions>
                <execution>
                    <id>compile-java25</id>
                    <goals>
                        <goal>compile</goal>
                    </goals>
                    <configuration>
                        <release>25</release>
                        <compileSourceRoots>
                            <compileSourceRoot>${project.basedir}/src/main/java25</compileSourceRoot>
                        </compileSourceRoots>
                        <multiReleaseOutput>true</multiReleaseOutput>
                    </configuration>
                </execution>
            </executions>
        </plugin>
        <plugin>
            <artifactId>maven-jar-plugin</artifactId>
            <version>3.5.1</version>
            <configuration>
                <archive>
                    <manifest>
                        <mainClass>com.example.App</mainClass>
                    </manifest>
                    <manifestEntries>
                        <Multi-Release>true</Multi-Release>
                    </manifestEntries>
                </archive>
            </configuration>
        </plugin>
    </plugins>
</build>

The default execution compiles src/main/java for Java 21. The compile-java25 execution compiles src/main/java25 for Java 25. The multiReleaseOutput parameter places the classes in META-INF/versions/25 inside the output directory. The JAR plugin adds the Multi-Release: true manifest entry, without which the runtime ignores the versioned classes, and sets the main class.

src/main/java/com/example/Greeter.java
package com.example;

public class Greeter {

    public static String greet() {
        return "Base version, running on Java " + Runtime.version().feature();
    }
}

The base version of the Greeter class runs on Java 21.

src/main/java25/com/example/Greeter.java
package com.example;

public class Greeter {

    public static String greet() {
        IO.println("Using the java.lang.IO class");
        return "Java 25 version, running on Java " + Runtime.version().feature();
    }
}

The Java 25 version of the same class uses the IO class. Both versions must have the same public API.

src/main/java/com/example/App.java
package com.example;

public class App {

    public static void main(String[] args) {
        System.out.println(Greeter.greet());
    }
}

The main class is shared by both versions.

The same JAR file prints a different message on Java 25 and on Java 21.

$ mvn package
[INFO] --- compiler:3.16.0:compile (default-compile) @ multirelease ---
[INFO] Compiling 2 source files with javac [debug release 21] to target/classes
[INFO] --- compiler:3.16.0:compile (compile-java25) @ multirelease ---
[INFO] Compiling 1 source file with javac [debug release 25] to target/classes/META-INF/versions/25
[INFO] --- jar:3.5.1:jar (default-jar) @ multirelease ---
[INFO] BUILD SUCCESS
...
$ jar tf target/multirelease-1.0.0.jar | grep class
META-INF/versions/25/com/example/Greeter.class
com/example/App.class
com/example/Greeter.class
$ java -jar target/multirelease-1.0.0.jar
Using the java.lang.IO class
Java 25 version, running on Java 25
$ ~/.sdkman/candidates/java/21.0.2-amzn/bin/java -jar target/multirelease-1.0.0.jar
Base version, running on Java 21

Annotation processors

Annotation processors, such as Lombok or MapStruct, generate code during compilation. Since JDK 23, javac no longer runs processors it finds on the class path, unless asked to. The reliable way is to list them in the annotationProcessorPaths parameter, which puts them on a separate processor path.

pom.xml (excerpt)
<properties>
    <maven.compiler.release>25</maven.compiler.release>
    <lombok.version>1.18.48</lombok.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${lombok.version}</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.16.0</version>
            <configuration>
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok</artifactId>
                        <version>${lombok.version}</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

Lombok is declared twice: as a provided dependency, so that its annotations are available to the compiler but are not part of the application, and as a processor path entry, so that its annotation processor runs. The version is shared through a property. Without the processor path, the build fails with cannot find symbol errors, because the generated methods do not exist; the <proc>full</proc> parameter would restore the old behaviour.

src/main/java/com/example/User.java
package com.example;

import lombok.Builder;
import lombok.Value;

@Value
@Builder
public class User {

    String name;
    String occupation;
    int age;
}

The @Value annotation generates a constructor, getters, equals, hashCode, and toString for an immutable class; @Builder generates a builder.

src/main/java/com/example/App.java
package com.example;

public class App {

    public static void main(String[] args) {
        var user = User.builder()
                .name("John Doe")
                .occupation("gardener")
                .age(34)
                .build();

        System.out.println(user);
        System.out.println(user.getName() + " is " + user.getAge());
    }
}

The application uses the generated builder and getters.

The WARNING lines are printed by the JDK, not by Maven: Lombok uses internal sun.misc.Unsafe methods, which JDK 24 and later report as deprecated for removal.

$ mvn compile
[INFO] Compiling 2 source files with javac [debug release 25] to target/classes
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
WARNING: sun.misc.Unsafe::objectFieldOffset has been called by lombok.permit.Permit
WARNING: Please consider reporting this to the maintainers of class lombok.permit.Permit
WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release
[INFO] BUILD SUCCESS
...
$ java -cp target/classes com.example.App
User(name=John Doe, occupation=gardener, age=34)
John Doe is 34

Useful parameters

These are some commonly used parameters of the compile goal with their user properties.

ParameterUser propertyDescription
releasemaven.compiler.releaseJava version to compile for (--release)
source, targetmaven.compiler.source, maven.compiler.targetLanguage level and bytecode version; both default to 8
compilerArgs—Additional javac options
failOnWarningmaven.compiler.failOnWarningFail the build on warnings (-Werror)
showDeprecationmaven.compiler.showDeprecationShow deprecation warnings
parametersmaven.compiler.parametersStore method parameter names (-parameters)
enablePreviewmaven.compiler.enablePreviewEnable preview language features
encodingencodingSource file encoding; defaults to project.build.sourceEncoding
procmaven.compiler.procAnnotation processing mode: none, only, or full

The optimize parameter seen in older POMs is deprecated and has no effect; the plugin documentation describes it as a no-op in javac. The complete list is printed by mvn help:describe -Dplugin=compiler -Ddetail.

Source

Apache Maven Compiler Plugin

In this article we have shown how to configure Java compilation with the Maven Compiler Plugin.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Java tutorials.