skip to content

When would you choose a JDK toolchain over the maven-compiler-plugin <release> flag, and what are the trade-offs?

level: seniorimportance: should knowfreq 35%

answer

  1. release = bytecode + API restriction, still running JDK
  2. toolchain = real target javac + test JVM
  3. release can't help for very old targets (JDK 8)
  4. toolchain certifies runtime fidelity
  5. combine: toolchain + release for language lock

basics

~20 s

Use <release> when you only need to target older bytecode and the running JDK's APIs are fine. Use a toolchain when you must compile and test with the real target JDK's compiler and runtime, not just emit older bytecode.

solid answer

~50 s

`<release>N` (since JDK 9) tells `javac` to produce bytecode for Java N AND restrict the visible API to that release — but it still uses the *running* JDK's compiler and runs tests on the *running* JVM. That is usually enough for libraries targeting an older language level when you trust the running JDK. A toolchain goes further: it uses the *actual* target JDK's `javac`, runtime, and test JVM, so behavior, JIT, GC, and runtime-only API differences are faithfully exercised. Choose a toolchain when you must certify the artifact actually runs and passes tests on the exact target JDK (e.g. customers on JDK 11 while CI runs JDK 21), or when you need a compiler/runtime that the modern JDK cannot emulate (e.g. building against JDK 8). Trade-offs: toolchains need every JDK installed and a maintained toolchains.xml on each machine; `<release>` needs nothing extra but is less faithful.

code

xml · 7 lines
xml
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <configuration>
    <release>11</release>
  </configuration>
</plugin>

go deeper

for a junior

Knows <release> sets the Java version for compilation.

for a middle

Distinguishes that <release> also restricts the API and that toolchains use a real separate JDK.

for a senior

Reasons about fidelity vs setup cost and when certification on the actual target JDK is required.

for a principal

Sets org policy: when toolchains are mandated (LTS certification matrices) vs when <release> suffices, balancing CI cost and risk.

## What `<release>` does The maven-compiler-plugin `<release>` configuration maps to `javac --release N` (available from JDK 9). It does three things at once: - sets the **source** language level to N, - sets the **target** bytecode version to N, - and crucially restricts the **API surface** so you can only reference classes/methods that existed in JDK N (it ships with profile metadata for old releases). What it does **not** do: change *which* `javac` runs, or which JVM runs your tests. Compilation uses the running JDK's compiler; Surefire runs tests on the running JVM. So if you run Maven on JDK 21 with `<release>11</release>`, you get Java-11 bytecode and a Java-11-restricted API, but the *runtime behavior* you test is JDK 21's. ```xml <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <release>11</release> </configuration> </plugin> ``` ## What a toolchain does differently A JDK toolchain points the compiler, Surefire, and Failsafe at a **real JDK 11 installation**. Compilation uses JDK 11's `javac`; tests run on JDK 11's JVM. You exercise the actual runtime: JIT, GC defaults, runtime-only behavior, reflection, and APIs as they truly are in 11. ## When to pick which - **Use `<release>`** when: you just need older bytecode/language level, the running JDK is recent enough, and you accept that runtime testing happens on the running JDK. Simplest — nothing to install. - **Use a toolchain** when: - You must **certify** the artifact compiles and *passes its test suite* on the exact JDK customers use. - You target a JDK so old that `--release` cannot help (e.g. building with JDK 8 while Maven runs on a newer JDK — modern `javac` may drop support for very old targets). - You need vendor-specific behavior (a particular vendor's JDK). - You want CI to run Maven on one fast modern JDK but produce/verify many target-JDK artifacts. ## Combining them They are complementary. A common pattern is a toolchain for the *real* JDK plus `<release>` to lock the language level, so even on the target JDK you cannot accidentally use newer language features. With a toolchain you can also use `source`/`target` instead of `release`. ## Trade-offs summary | | `<release>` | Toolchain | |---|---|---| | Compiler used | running JDK's javac | target JDK's javac | | Test JVM | running JVM | target JVM | | Setup cost | none | install JDKs + maintain toolchains.xml | | Fidelity | bytecode/API only | full compile+runtime | ## Bottom line `<release>` is about *what bytecode and API you emit*; a toolchain is about *which actual JDK builds and runs it*. For true cross-JDK certification, use a toolchain.

  • Can you use both a toolchain and <release> together?
    Yes. The toolchain picks the real JDK; <release> additionally locks the language level/API so you cannot accidentally use newer features even on that JDK.
  • Why can't <release> always replace building on JDK 8 directly?
    Modern javac drops --release support for very old targets, and runtime behavior (GC/JIT/runtime APIs) is only faithfully tested on a real JDK 8 via a toolchain.

saying these in an interview costs you the question

  • Claiming <release> runs your tests on the target JDK — it runs them on the JDK that launched Maven.
  • Treating toolchain and <release> as mutually exclusive; they are complementary.

context