openrewrite / openrewrite/rewrite
Maven 4 transitive dependencyManagement changes resolved versions vs. rewrite-maven
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 3.7k
- Forks
- 570
- Avg merge
- 13h 12m
- Merged PRs (30d)
- 261
Description
Problem
Maven 4 changed which <dependencyManagement> sections affect transitive dependency versions, and rewrite-maven follows the Maven 3 rule. On a project built with Maven 4, our LST can therefore record different transitive versions than the build actually resolves.
- Maven 3 wires
ClassicDependencyManager— only the root/current project'sdependencyManagementcontributes managed versions.dependencyManagementdeclared in intermediate dependency POMs is ignored. - Maven 4 wires
TransitiveDependencyManagerby default —dependencyManagementis collected at every depth and applied from depth ≥ 2, so an intermediate POM'sdependencyManagementnow governs its own transitives. Nearer-to-root still wins on conflict.
Verified divergence
Four synthetic artifacts, installed to a local repo:
dm-c:1.0, dm-c:2.0 leaf, no dependencies
dm-b:1.0 -> dm-c:1.0
dm-a:1.0 -> dm-b:1.0, and <dependencyManagement> pins dm-c to 2.0
dm-app:1.0 -> dm-a:1.0 (no dependencyManagement of its own)
Resolving dm-app, same POMs, same local repository:
| Resolver | dm-c resolves to |
|---|---|
Maven 3.9.16 dependency:tree |
1.0 |
Maven 4.0.0-rc-5 dependency:tree |
2.0 |
rewrite-maven MavenResolutionResult |
1.0 |
# Maven 3.9.16
io.example.dmtest:dm-app:jar:1.0
\- io.example.dmtest:dm-a:jar:1.0:compile
\- io.example.dmtest:dm-b:jar:1.0:compile
\- io.example.dmtest:dm-c:jar:1.0:compile
# Maven 4.0.0-rc-5 (no flags)
io.example.dmtest:dm-app:jar:1.0
\- io.example.dmtest:dm-a:jar:1.0:compile
\- io.example.dmtest:dm-b:jar:1.0:compile
\- io.example.dmtest:dm-c:jar:2.0:compile
This is the same shape as Maven's own integration test mng-7982-transitive-dependency-management (MavenITmng7982DependencyManagementTransitivityTest).
Scope is narrower than it first looks
A control with the same dependency graph, but the dm-c pin moved to the root project's dependencyManagement, resolves to 2.0 under both Maven 3 and Maven 4. So:
- Root-project
dependencyManagementand imported BOMs — the common case — are unaffected. - Only
dependencyManagementdeclared in intermediate/transitive POMs diverges. - Managed scope and optional are still root-only in Maven 4 (
isInheritedDerived()returnsdepth == 0); only version, exclusions and localPath became transitive.
Where we sit today
ResolvedPom.getValues(Dependency, int depth) (~line 1313):
if (d.getVersion() == null || depth > 0) {
// dependency management overrides transitive dependency versions
version = getManagedVersion(d.getGroupId(), d.getArtifactId(), d.getType(), d.getClassifier());
getManagedVersion reads ResolvedPom.this.dependencyManagement — the root project's resolved management, including its parents and imported BOMs. Intermediate POMs' management is never consulted. That is ClassicDependencyManager, and it is correct for Maven 3.
The design question
This isn't a straightforward bug fix, because nothing in the POM tells us which resolver a project uses. A modelVersion 4.0.0 POM builds fine under Maven 4, so model version is not a signal. Options, roughly:
- Keep Maven 3 semantics — correct today, increasingly wrong as Maven 4 adoption grows.
- Switch to Maven 4 semantics — wrong for the large majority still on Maven 3.
- Make it configurable on
MavenExecutionContextView, defaulting to Maven 3 for now. - Infer from the project:
.mvn/wrapper/maven-wrapper.propertiesdistributionUrl, enforcerrequireMavenVersion, or.mvn/maven.config. Best-effort and fallible, but it is real signal, and the parser already reads.mvn/-adjacent files in places.
Worth noting for whichever route: Maven 4 users can opt back into the old behavior with -Dmaven.resolver.dependencyManagerTransitivity=false (or .mvn/maven.config), so "is this project on Maven 4" isn't sufficient on its own either.
Impact
Silent. Nothing errors — we just record a different version than the build uses. That feeds anything reading transitive versions: UpgradeTransitiveDependencyVersion, dependency insight/vulnerability searches, and dependency-graph reporting.
Low blast radius today given Maven 4 adoption, which is why I'd rather we pick a direction deliberately than discover it through a support ticket.
Related
- apache/maven#12302 — the user-visible symptom upstream (enforcer
RequireUpperBoundDepsfailures from downgraded transitives). Closed, but the fix PR apache/maven#12316 was closed unmerged and the behavior is unchanged as of 4.0.0-rc-6. - Maven 4.0.0-rc-6 release notes call this out under "Transitive dependency resolution changes".
- #6869, #8345 — other Maven 4 gaps, unrelated code paths.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at ResolvedPom.getValues(Dependency, int depth) around line 1313 and its getManagedVersion call; compare that path with Maven's mng-7982-transitive-dependency-management test and the Maven 3/4 synthetic graph described here. First determine the resolver-selection or configuration behavior; done means an agreed approach is covered by tests for intermediate-POM management without changing root-project or imported-BOM behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- build-system
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 35/100