skip to content

How do you stop a specific transitive dependency from being pulled in, and when would you do that?

level: middleimportance: must knowfreq 80%

answer

  1. <exclusions> inside the offending direct dep
  2. needs only groupId + artifactId, no version
  3. wildcard * (Maven 3.2.1+)
  4. logging clashes / CVE / duplicates
  5. per-declaration, not global

basics

~20 s

Add an <exclusions> block inside the direct dependency that drags it in, naming the unwanted groupId and artifactId. That prunes it (and its sub-tree) from the graph. Use it for clashing logging libs or vulnerable transitives.

solid answer

~40 s

You attach an `<exclusions>` element to the **direct dependency whose sub-tree contains the unwanted artifact**, listing each unwanted `<groupId>`/`<artifactId>`. This prunes that node — and everything reachable only through it — from the resolved graph. Common reasons: removing a conflicting logging implementation (e.g. excluding `commons-logging` in favor of SLF4J bridges), dropping a transitive with a known CVE, or eliminating a duplicate/relocated artifact. Since Maven 3.2.1 you can use the wildcard `*` for groupId/artifactId to exclude an entire transitive sub-tree at once. Exclusions are surgical and live at the consumer side; the alternative for version (not removal) control is `<dependencyManagement>`. After excluding, re-run `mvn dependency:tree` to confirm the branch is gone and that you didn't accidentally remove something still needed at runtime.

code

xml · 11 lines
xml
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-core</artifactId>
  <version>6.1.0</version>
  <exclusions>
    <exclusion>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
    </exclusion>
  </exclusions>
</dependency>

go deeper

for a junior

Know that <exclusions> removes an unwanted transitive and lives inside a dependency block.

for a middle

Place it correctly, omit version, use wildcards, and re-verify with dependency:tree.

for a senior

Pick exclusion vs dependencyManagement appropriately and weigh runtime ClassNotFound risk.

for a principal

Avoid scattershot exclusions across many POMs; centralize control in a parent/BOM and enforce convergence.

## The mechanism `<exclusions>` removes a transitive artifact from the graph. You place it **inside the direct dependency that brings the unwanted thing in** — not at the top level on its own. ```xml <dependency> <groupId>com.example</groupId> <artifactId>big-lib</artifactId> <version>1.0</version> <exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency> ``` An `<exclusion>` needs only `groupId` + `artifactId` (no version). Excluding a node also removes the part of the sub-tree reachable **only** through it. ## Wildcards (Maven 3.2.1+) ```xml <exclusion> <groupId>*</groupId> <artifactId>*</artifactId> </exclusion> ``` This strips **all** transitive dependencies of that direct dependency — useful for a fat/shaded artifact that already bundles its deps, or to force you to declare exactly what you want. ## When to use it - **Logging clashes**: e.g. exclude `commons-logging` so the SLF4J `jcl-over-slf4j` bridge can stand in; or exclude `log4j` for `log4j-to-slf4j`. - **Security**: cut a transitive with a known CVE and replace it with a patched version declared directly. - **Duplicate/relocated artifacts**: e.g. `javax.*` vs `jakarta.*`, or `google-collections` vs `guava`. - **Classpath bloat**: remove a heavy unused branch. ## Caveats - If something at runtime still needs the excluded class, you'll get `ClassNotFoundException`. Verify with tests. - Exclusion is **per declaring dependency** — if three direct deps each drag in the same artifact, you must exclude it on each (or use `dependencyManagement`/enforcer for graph-wide control). - Exclusions **remove**; to merely change a version, prefer `<dependencyManagement>`. ## Verify ```bash mvn dependency:tree ``` Confirm the artifact and its dependents are gone.

  • Can you exclude an entire transitive sub-tree of one dependency at once?
    Yes, since Maven 3.2.1 use <groupId>*</groupId><artifactId>*</artifactId> as the exclusion to strip all of that dependency's transitives.
  • An artifact is dragged in by three different direct dependencies. What does one <exclusions> block achieve?
    It only prunes it from that one declaring dependency. You must add the exclusion to each path, or control it globally via dependencyManagement / the enforcer plugin.

saying these in an interview costs you the question

  • Putting <version> inside <exclusion> — exclusions match by groupId/artifactId only.
  • Placing the exclusion as a standalone top-level element rather than inside the dependency that pulls it in.
  • Using exclusions to downgrade/upgrade a version — that's dependencyManagement's job; exclusion only removes.

context