skip to content

How does Gradle decide which registered transform to apply, and when does it chain multiple transforms?

level: seniorimportance: should knowfreq 30%

answer

  1. transforms = edges from→to
  2. graph search for a path
  3. chains composed automatically
  4. unmentioned attributes pass through
  5. ambiguity → add attributes to disambiguate

basics

~20 s

Gradle compares an artifact's attributes to the requested attributes. If they differ, it searches registered transforms for a chain whose from matches the source and whose to reaches the request, applying one or several in sequence.

solid answer

~40 s

When a resolution requests attributes that no available variant directly satisfies, Gradle's matching engine treats registered transforms as edges in a graph: each transform is an edge from its `from` attributes to its `to` attributes. It searches for a path from a producer variant to the requested attribute set, preferring the shortest chain and the most specific match. If a single transform bridges the gap, it inserts that; if not, it can **chain** transforms (A→B, then B→C) automatically. Ambiguity — two equally valid chains — is an error you must disambiguate by adding more attributes so exactly one path matches. Only attributes explicitly set on `from`/`to` participate; unmentioned attributes are passed through unchanged, which is what lets Gradle preserve things like the `org.gradle.usage` of the original variant while changing only `artifactType`.

code

kotlin · 14 lines
kotlin
val artifactType = Attribute.of("artifactType", String::class.java)
val minified = Attribute.of("minified", Boolean::class.javaObjectType)

dependencies {
    // jar -> minified jar; only 'minified' changes, artifactType preserved
    registerTransform(Minify::class) {
        from.attribute(minified, false).attribute(artifactType, "jar")
        to.attribute(minified, true).attribute(artifactType, "jar")
    }
}

val minifiedJars = configurations.runtimeClasspath.get().incoming
    .artifactView { attributes.attribute(minified, true) }
    .artifacts.artifactFiles

go deeper

for a junior

Know Gradle picks the transform automatically based on attributes; details of chaining are beyond this level.

for a middle

Explain that a transform matches when its from fits the artifact and to fits the request, and that unmentioned attributes pass through.

for a senior

Explain graph-search matching, automatic chaining, pass-through, and resolving ambiguity with custom attributes.

for a principal

Discuss designing a coherent attribute schema so transform matching across a large build stays deterministic and ambiguity-free.

## Matching as graph search Gradle's dependency resolution selects a **variant** of each component, where a variant is a set of artifacts plus a set of **attributes**. When you request a target attribute set (via `artifactView` or a configuration's attributes), Gradle first tries direct variant matching. If no variant matches exactly, it brings **registered transforms** into play. Think of each registered transform as a directed edge: `from-attributes → to-attributes`. The engine searches for a path from some available producer variant to the requested attributes: - A **single transform** is used when its `from` matches the producer and its `to` matches (or, combined with pass-through attributes, satisfies) the request. - A **chain** is used when no single transform spans the gap but a sequence does: e.g. `jar → classes` then `classes → instrumented-classes`. Gradle composes them automatically; you never wire the chain by hand. ## Pass-through attributes A transform only touches the attributes it explicitly declares in `from`/`to`. Every other attribute on the source variant is **carried through unchanged** to the output. This is essential: a `jar → classes` transform changes `artifactType` but preserves `org.gradle.usage`, `org.gradle.category`, etc., so the transformed artifact still slots into the right place in the graph. ## Specificity and shortest path When several paths exist, Gradle prefers the more specific / shorter chain. But if two chains are genuinely equivalent, you get an **ambiguous transformation** error. The fix is to add attributes so that exactly one chain is selectable — e.g. introduce a custom `minified` attribute (`true`/`false`) and request the value you want. ```kotlin val minified = Attribute.of("minified", Boolean::class.javaObjectType) dependencies { registerTransform(Minify::class) { from.attribute(minified, false).attribute(artifactType, "jar") to.attribute(minified, true).attribute(artifactType, "jar") } } ``` Here `artifactType` stays `jar`; only the custom `minified` attribute flips, so the request `attribute(minified, true)` selects exactly this transform. ## Implications - Keep `from`/`to` minimal and meaningful so matching stays unambiguous. - Introduce custom attributes when `artifactType` alone can't distinguish source from target. - Remember attributes you don't mention are preserved — don't redeclare them on `to` unless you intend to change them.

  • What happens to attributes you don't declare in from/to?
    They pass through unchanged. The transform only alters the attributes it explicitly names, preserving things like org.gradle.usage so the output still fits the graph.
  • You get an 'ambiguous transformation' error. How do you fix it?
    Two equally valid transform chains satisfy the request. Add a distinguishing attribute (custom or otherwise) to from/to and request its value so exactly one chain matches.
  • Can Gradle apply more than one transform to a single artifact?
    Yes. If no single transform bridges from the producer attributes to the request, Gradle composes a chain (A→B then B→C) automatically.

It's like flight routing: each transform is a flight leg between two airports (attribute sets). If there's no direct flight, Gradle books connecting legs; if two equally good itineraries exist, it refuses to guess and asks you to pin one.

saying these in an interview costs you the question

  • Believing you must explicitly register chained transforms — Gradle composes them.
  • Redeclaring every attribute on `to`, accidentally overriding pass-through attributes.
  • Ignoring ambiguity errors instead of adding a disambiguating attribute.

context