skip to content

Within a single dependency declaration, how do you turn off transitive resolution or exclude a specific transitive module, and what's the difference between the two?

level: seniorimportance: should knowfreq 50%

answer

  1. configuring block on the declaration
  2. isTransitive=false drops ALL transitives
  3. exclude(group, module) drops ONE
  4. exclude can take group-only or module-only
  5. you re-declare needs after isTransitive=false

basics

~10 s

Pass a configuring block to the declaration. isTransitive = false drops ALL transitive dependencies of that module; exclude(group = ..., module = ...) removes only a specific transitive module while keeping the rest.

solid answer

~50 s

Both options are set in the **trailing configuration block** of a single dependency declaration, operating on the resulting `ModuleDependency`/`ExternalModuleDependency`. **`isTransitive = false`** tells Gradle to bring in *only* that module's own artifact and **none** of its transitive dependencies — a blunt, all-or-nothing switch. **`exclude(group = "...", module = "...")`** is surgical: it drops one (or a group of) transitive module(s) from that dependency's graph while keeping every other transitive. You can pass `group` only, `module` only, or both. Use `exclude` when one transitive conflicts or is unwanted (e.g. excluding `commons-logging` in favor of a bridge); use `isTransitive = false` only when you truly intend to manage every transitive yourself — it's brittle because you must then re-declare anything the module actually needs. Both are scoped to this declaration; they don't change global resolution. (Build-wide exclusions and richer transitivity strategy belong to other mechanisms — here we mean the per-declaration form.)

code

kotlin · 10 lines
kotlin
dependencies {
    implementation("org.springframework:spring-context:6.1.5") {
        // surgical: remove just commons-logging
        exclude(group = "commons-logging", module = "commons-logging")
    }
    implementation("com.example:heavy-sdk:2.0") {
        // blunt: no transitives at all
        isTransitive = false
    }
}

go deeper

for a junior

Know that a dependency can have a block where you can turn off transitives or exclude something; exact API not expected.

for a middle

State isTransitive = false drops all transitives and exclude(group, module) drops a named one, with a correct example.

for a senior

Contrast scope/risk, give a real exclude use case (commons-logging bridge), and explain the runtime-breakage risk of isTransitive = false.

for a principal

Weigh per-declaration knobs against build-wide strategies (constraints, capabilities, substitution), and the maintainability cost of scattered excludes across a large multi-module build.

## Where these options live A dependency declaration accepts a trailing **configuration block** (closure in Groovy, lambda in Kotlin). Inside it `this` is the created dependency — a `ModuleDependency` (or `ExternalModuleDependency`) — exposing `isTransitive` and `exclude(...)`: ```kotlin dependencies { implementation("org.hibernate:hibernate-core:6.4.4.Final") { // surgical removal of ONE transitive module exclude(group = "org.jboss.logging", module = "jboss-logging") } implementation("com.example:fat-lib:1.0") { // drop ALL transitives of fat-lib isTransitive = false } } ``` ## `isTransitive = false` — all or nothing By default a module brings its declared transitive dependencies. Setting `isTransitive = false` on the declaration tells Gradle to resolve **only that module's own artifact** and ignore its entire transitive graph. Consequences: - You get a smaller, fully-controlled set — but you are now responsible for declaring *every* transitive the library genuinely needs at runtime, or you'll hit `ClassNotFoundException`/`NoClassDefFoundError`. - It's a blunt instrument; prefer it only when you intentionally vendor/manage the whole tree yourself. ## `exclude(group, module)` — surgical `exclude` removes specific transitive modules from *this* dependency's subgraph, keeping the rest: - `exclude(group = "commons-logging", module = "commons-logging")` — drop exactly that module. - `exclude(group = "com.example")` — drop *all* modules from that group (module omitted). - `exclude(module = "unwanted-lib")` — drop that module regardless of group. Typical reasons: a transitive conflicts with another version, you're swapping in a bridge/replacement (e.g. `jcl-over-slf4j` for `commons-logging`), or a transitive is unused and bloats the classpath. ## Key difference | | `isTransitive = false` | `exclude(...)` | |--|------------------------|----------------| | Scope | the dependency's **entire** transitive graph | **selected** transitive module(s) | | Granularity | all-or-nothing | per-group / per-module | | Risk | high — you must re-add real needs | lower — only removes what you name | | Use when | you fully manage the tree | one transitive is unwanted/conflicting | ## Both are per-declaration These options affect only **this** dependency declaration's view of the graph; they don't impose a build-wide rule. If two declarations pull the same library, you'd configure each (or use a higher-level mechanism). Within the *notation* topic, the point is: the configuring block is part of how you *declare* a dependency, and `isTransitive`/`exclude` are the two knobs you set there. ## Speakable summary "`isTransitive = false` cuts off the whole subtree; `exclude` snips a single branch. I default to `exclude` for a known conflict and reserve `isTransitive = false` for when I deliberately own the entire transitive set."

  • What can go wrong after setting `isTransitive = false`?
    The module's required transitive dependencies are no longer on the classpath, so you can hit ClassNotFoundException/NoClassDefFoundError at runtime unless you re-declare each real need yourself.
  • How do you exclude every module from a group rather than one specific module?
    Pass only `group` to `exclude`, e.g. `exclude(group = "com.example")` — omitting `module` matches all modules in that group.
  • Why is `exclude` usually preferred over `isTransitive = false`?
    It's surgical — it removes only the named conflicting/unwanted transitive and keeps everything else the library legitimately needs, so it's far less likely to break the runtime classpath.

isTransitive = false is unplugging a power strip — everything downstream goes dark. exclude is flipping one outlet off while the rest stay powered.

saying these in an interview costs you the question

  • Saying `exclude` and `isTransitive = false` are interchangeable — one is surgical, the other all-or-nothing.
  • Forgetting that `isTransitive = false` forces you to manually supply the module's real runtime needs.
  • Claiming a per-declaration exclude is build-wide — it only affects that declaration's subgraph.

context