How do you stop a specific transitive dependency from being pulled in, and when would you do that?
answer
- <exclusions> inside the offending direct dep
- needs only groupId + artifactId, no version
- wildcard * (Maven 3.2.1+)
- logging clashes / CVE / duplicates
- per-declaration, not global
basics
~20 sAdd 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 sYou 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<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
Know that <exclusions> removes an unwanted transitive and lives inside a dependency block.
Place it correctly, omit version, use wildcards, and re-verify with dependency:tree.
Pick exclusion vs dependencyManagement appropriately and weigh runtime ClassNotFound risk.
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.