skip to content

How does the TargetJvmVersion attribute drive variant selection for libraries that support multiple JVM targets?

level: middleimportance: should knowfreq 40%

answer

  1. org.gradle.jvm.version integer
  2. compatible when P <= C
  3. disambiguation: highest compatible
  4. consumer value from toolchain/release
  5. too-low request -> no matching variant

basics

~20 s

Each variant declares the JVM version it targets via TargetJvmVersion. A variant built for an older JVM is compatible with a consumer on a newer one, and Gradle picks the highest compatible version for your build.

solid answer

~50 s

`org.gradle.jvm.version` (`TargetJvmVersion`) records the minimum JVM a variant's bytecode runs on. Its **compatibility rule** says: a producer variant targeting version P is compatible with a consumer requesting version C when P <= C — because a newer JVM can execute older bytecode, but not vice versa. Its **disambiguation rule** then prefers the **highest** compatible P, giving you the most modern artifact your JVM can still run. The Java plugin derives the consumer's requested value from your toolchain / `release` / source-target compatibility. So a library that publishes JVM-8 and JVM-17 variants will resolve to the 8 variant for a JVM-8 build and the 17 variant for a JVM-17 build, from the *same* dependency coordinate. If you request a JVM lower than any published variant, you get a 'no matching variant' failure pointing at the JVM-version attribute.

code

kotlin · 7 lines
kotlin
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}
// Compile/runtime classpaths now request TargetJvmVersion 17;
// a multi-target library resolves to its highest compatible (<= 17) variant.

go deeper

for a junior

Know that libraries can target different JVMs and Gradle picks one matching your Java version.

for a middle

State the compatibility direction (P <= C) and that disambiguation prefers the highest, and that the value comes from the toolchain.

for a senior

Explain how a toolchain bump can silently change selected variants and transitive graphs, and how to verify with dependencyInsight.

for a principal

Set org policy on JVM baselines and toolchains so variant selection stays consistent across many modules and CI agents.

## What TargetJvmVersion encodes `TargetJvmVersion` (attribute id `org.gradle.jvm.version`) is an integer attribute on a variant stating the **minimum JVM major version** that variant supports — e.g. 8, 11, 17, 21. It exists because one library can ship multiple builds (multi-release jars or distinct variants) compiled for different JVM baselines. ## The built-in rules Gradle registers standard compatibility and disambiguation rules for this attribute in the `attributesSchema`: - **Compatibility**: producer value `P` is compatible with consumer request `C` iff `P <= C`. Rationale: a JVM at version C can run bytecode targeting any `P <= C`. A variant needing JVM 21 is *incompatible* with a consumer on 17. - **Disambiguation**: among compatible variants, choose the **highest** `P`. This gives the most up-to-date artifact runnable on the consumer's JVM. ## Where the consumer value comes from The Java/Java-Library plugin computes the consumer's requested `TargetJvmVersion` from your build's effective target: the **Java toolchain** language version, or `release`, or `sourceCompatibility`/`targetCompatibility`. So you rarely set it by hand — configuring the toolchain is enough. ## End-to-end example A library publishes (via Gradle Module Metadata) `runtimeElements` variants tagged JVM 8 and JVM 17. - Consumer toolchain = 8 -> only the JVM-8 variant is compatible -> selected. - Consumer toolchain = 17 -> both compatible -> disambiguation picks JVM 17. - Consumer toolchain = 7 -> neither compatible -> **no matching variant**, error names `org.gradle.jvm.version`. ```kotlin java { toolchain { languageVersion = JavaLanguageVersion.of(17) } } // Gradle now requests TargetJvmVersion 17 on compile/runtime classpaths, // so multi-target libraries resolve to their JVM-17 variant when available. ``` ## Why it matters This is how Gradle delivers *automatic, correct* artifact selection for libraries that straddle JVM versions, without classifiers or manual switches — and why bumping a toolchain can change which variant (and transitive deps) you pull.

  • Why is requesting a lower JVM than the producer offers a failure, but requesting a higher one fine?
    Compatibility is P <= C: a variant built for JVM 21 cannot run on a consumer at 17 (higher P, lower C) so it's rejected; a variant for 8 runs on 17 so it's accepted.
  • How does Gradle decide the consumer's requested TargetJvmVersion?
    The Java plugin derives it from the effective target — the Java toolchain language version, or `release`, or source/target compatibility — and applies it to the resolvable classpath configurations.

Like choosing a video file your player supports: an older codec plays everywhere, but among supported ones you pick the highest-quality the device can still handle.

saying these in an interview costs you the question

  • Saying a higher-JVM variant is acceptable for a lower-JVM consumer (gets the direction backwards).
  • Claiming disambiguation prefers the lowest JVM version.

context