How does Gradle decide which registered transform to apply, and when does it chain multiple transforms?
answer
- transforms = edges from→to
- graph search for a path
- chains composed automatically
- unmentioned attributes pass through
- ambiguity → add attributes to disambiguate
basics
~20 sGradle 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 sWhen 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 linesval 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.artifactFilesgo deeper
Know Gradle picks the transform automatically based on attributes; details of chaining are beyond this level.
Explain that a transform matches when its from fits the artifact and to fits the request, and that unmentioned attributes pass through.
Explain graph-search matching, automatic chaining, pass-through, and resolving ambiguity with custom attributes.
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.