What is the difference between configuring <release> and <source>/<target> in the maven-compiler-plugin, and which should you prefer today?
answer
- source=syntax, target=bytecode
- release also swaps API signatures
- NoSuchMethodError at runtime trap
- --release since JDK 9
- maven.compiler.release property
basics
~10 s<source>/<target> set the language level and bytecode version separately. <release> sets both at once AND links against that JDK's API, preventing accidental use of newer APIs. Prefer <release>.
solid answer
~40 s<source> tells javac which Java language features to accept; <target> tells it which bytecode version to emit. The problem: with a newer JDK you can compile with <source>/<target>=11 but still call methods that only exist in the JDK 17 class library, because javac links against the running JDK's rt classes. The result compiles fine but fails with NoSuchMethodError at runtime on Java 11. <release> (javac's --release flag, since JDK 9) fixes this: it sets source, target, AND swaps in the signature data for that release's API, so you get a compile error if you touch a too-new API. Set it once: maven.compiler.release=17 or the plugin's <release>17</release>. Prefer <release> on JDK 9+; fall back to <source>/<target> only when cross-compiling needs differ or on JDK 8.
code
xml · 12 lines<properties>
<maven.compiler.release>17</maven.compiler.release>
</properties>
<!-- equivalent to: -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>17</release>
</configuration>
</plugin>go deeper
Knows you set a Java version via maven.compiler.release (or source/target) and that target controls which JVM can run the jar.
Explains source vs target vs release precisely and that release links against the historical API.
Articulates the NoSuchMethodError cross-compile trap and chooses release, knowing the JDK 8 / differing-version exceptions.
Standardizes a single release property across a multi-module parent POM and a JDK toolchain policy to keep builds reproducible across developer machines.
## What the compiler plugin does Maven itself does not compile code; the `maven-compiler-plugin` wraps `javac` and binds to the `compile` and `test-compile` phases. You configure it to control how `javac` runs. ## Three knobs - `<source>` — the **language level**: which syntax/features javac will accept (e.g. records need 16+, `var` needs 10+). - `<target>` — the **bytecode version** written into `.class` files; determines the minimum JVM that can load them. - `<release>` — a single value that sets source AND target AND, crucially, makes javac compile **against the public API of that exact Java release** rather than the JDK you happen to be running. ## The bug <release> solves Suppose you run the build on JDK 17 but want to ship for Java 11. With `<source>11</source><target>11</target>`, javac accepts only Java 11 syntax and emits Java 11 bytecode — but it still resolves method/type references against **JDK 17's** class library. So a call to an API method introduced in Java 12+ compiles cleanly, then throws `NoSuchMethodError`/`NoClassDefFoundError` on a real Java 11 JVM. `--release 11` (exposed as `<release>`) bundles historical API signature files, so that same call becomes a **compile-time error**. This is the right, safe behaviour. ## How to set it The simplest form is a property; the plugin reads it automatically: ```xml <properties> <maven.compiler.release>17</maven.compiler.release> </properties> ``` Equivalent explicit plugin config: ```xml <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <release>17</release> </configuration> </plugin> ``` ## When NOT to use <release> - **JDK 8 builds**: `--release` exists only on JDK 9+, so on an 8 toolchain use `<source>1.8</source><target>1.8</target>`. - You need a **different source vs target** (rare). `<release>` forces them equal. - You need `--release` plus `--add-exports`/module hacks for internal JDK APIs that `--release` deliberately hides — then `<source>/<target>` plus extra `<compilerArgs>` may be required. ## Recommendation On any JDK 9+ build targeting a single Java version, **use `<release>`**. It is the only option that actually prevents shipping code that references APIs newer than your target.
- Why can code compile with <source>/<target>=11 on a JDK 17 build yet fail at runtime?javac links method references against the running JDK's (17's) class library, so calls to APIs added after 11 resolve at compile time but are missing on a real Java 11 JVM, causing NoSuchMethodError.
- When would you still use <source>/<target> instead of <release>?On JDK 8 (no --release flag), when source and target must differ, or when you need access to internal JDK APIs that --release hides.
<source>/<target> is like writing in 2011 English but with a 2017 dictionary on your desk — you may slip in a word that didn't exist yet. <release> hands you only the 2011 dictionary.
saying these in an interview costs you the question
- Claiming <source>/<target> guards against using newer-than-target APIs — it does not.
- Saying <release> only works on JDK 8 (it's the opposite: JDK 9+).
- Thinking <target> controls language features — that's <source>.