Can finalizedBy create a circular dependency, and how does the finalization direction interact with the task graph?
answer
- finalizedBy = forward-only pull (finalized pulls finalizer)
- single finalizedBy edge can't cycle
- contradiction with opposite dependsOn => circular error
- Gradle prints the cycle chain, fails fast
- only shouldRunAfter is dropped to break cycles
basics
~10 sFinalization is one-directional: requesting a finalizer never pulls in its finalized task. But combining finalizedBy with dependsOn carelessly can create a true cycle, which Gradle rejects with a circular-dependency error.
solid answer
~40 s`finalizedBy` adds an edge 'finalized → finalizer' (finalizer runs after). It is **forward-only for pulling**: scheduling the finalized task pulls in the finalizer, but scheduling the finalizer does **not** pull in the finalized task. A cycle arises only if you create conflicting edges, e.g. `A.finalizedBy(B)` plus `B.dependsOn(A)` is fine (both say A-before-B), but `A.finalizedBy(B)` plus `A.dependsOn(B)` is contradictory — B must run both before and after A — and Gradle reports a **circular dependency**. Gradle detects cycles when building the graph and fails fast with the chain printed. So finalizedBy alone can't loop, but mixing it with dependsOn/mustRunAfter in opposite directions can. The mental model: every relationship is a directed edge; a cycle among the hard edges (`dependsOn`, `finalizedBy`, `mustRunAfter`) is illegal. `shouldRunAfter` is the only one Gradle will *drop* to avoid a cycle.
code
kotlin · 6 lines// Legal: both edges say a-before-b
tasks.named("a") { finalizedBy("b") }
tasks.named("b") { dependsOn("a") }
// Illegal cycle: b must run after AND before a
// tasks.named("a") { finalizedBy("b"); dependsOn("b") } // -> circular dependencygo deeper
Not expected — this is graph-edge nuance beyond basic usage.
Know finalization is forward-only and that mixing opposite-direction edges can cycle.
Explain the edge model, which edges pull, and how/when cycles are reported vs dropped.
Reason about graph determinism and how convention plugins should avoid introducing contradictory edges across modules.
## Edges, direction, and pulling Gradle's execution plan is a DAG. Each relationship contributes a directed edge: - `B.dependsOn(A)` → edge A→B, and **pulls A in** when B is requested. - `A.finalizedBy(B)` → edge A→B (B after A), and **pulls B in** when A is requested. - `B.mustRunAfter(A)` / `B.shouldRunAfter(A)` → edge A→B for ordering, **pulls nothing in**. Note `dependsOn` and `finalizedBy` both produce an A→B edge but differ in *which end pulls the other in*: `dependsOn` pulls the predecessor in (you request B, A is pulled), `finalizedBy` pulls the successor in (you request A, B is pulled). ## Can finalizedBy alone cycle? No. A single `A.finalizedBy(B)` edge can't form a loop by itself. You'd need a path back from B to A. That requires another relationship. ## How cycles actually appear ```kotlin tasks.named("a") { finalizedBy("b") } // a -> b tasks.named("a") { dependsOn("b") } // b -> a (contradiction!) ``` Now b must run after a (finalizer) **and** before a (dependency). That's a cycle a↔b. Gradle fails: ``` Circular dependency between the following tasks: :a \--- :b \--- :a (*) ``` Gradle builds the plan eagerly enough to detect this and aborts before executing. ## A non-cycle that looks scary ```kotlin tasks.named("a") { finalizedBy("b") } // a -> b tasks.named("b") { dependsOn("a") } // a -> b (same direction!) ``` Both edges say a-before-b. **No cycle** — perfectly legal, just redundant-ish. ## shouldRunAfter is special Among ordering relationships, only `shouldRunAfter` is *soft*: if honoring it would create a cycle, Gradle **drops** the constraint rather than failing. `mustRunAfter`, `dependsOn`, and `finalizedBy` are hard edges that, if they form a cycle, abort the build. ## Finalizer chains and graphs A finalizer can have its own finalizers and dependencies, forming arbitrarily deep structures — all still constrained to be acyclic among hard edges. Each finalizer runs after its finalized task per the standard rule. ## Practical takeaways - Reach for `finalizedBy` only to express 'run after, even on failure'. Don't also add a contradicting `dependsOn`/`mustRunAfter` in the opposite direction. - If you hit a circular-dependency error, read the printed chain; it tells you exactly which edges conflict. - Remember finalization is forward-only for pulling — don't expect requesting the finalizer to drag the finalized task into the build.
- If requesting a finalizer doesn't pull in the finalized task, what runs when you invoke only the finalizer?Just the finalizer (and its own dependencies). Finalization is forward-only: the finalized task is not pulled in, so it doesn't run.
- Which relationship does Gradle silently drop to avoid a cycle?shouldRunAfter. It's the only soft ordering edge; mustRunAfter, dependsOn, and finalizedBy are hard and will cause a circular-dependency failure instead.
saying these in an interview costs you the question
- Claiming finalizedBy by itself can create a circular dependency.
- Thinking requesting the finalizer pulls in the finalized task.
- Believing Gradle drops mustRunAfter or finalizedBy to break cycles — only shouldRunAfter is dropped.