Repository navigation
Configurable Dependency Version Resolution Strategy #11391
Description
Summary
Currently, Maven's resolver treats direct dependencies (declared in a POM) as absolute version requirements that always override transitive dependencies, even when transitive deps require higher versions. This requires extensive use of <dependencyManagement> blocks to control resolution. This RFE proposes a configurable mechanism to allow more flexible version resolution while maintaining user control.
Motivation
- Current Pain Point: Developers must explicitly manage many transitive dependencies through
<dependencyManagement>to avoid version conflicts - Flexibility Need: Different projects have different requirements—some need strict version control, others prefer automatic upgrades to satisfy transitive requirements
- Control Concern: Any change to default resolution behavior risks breaking builds unexpectedly (e.g., in reactor builds where in-development versions could be downgraded)
Proposed Solution
Introduce an optional directDependencyStrategy attribute at both project and dependency levels to control how direct dependencies participate in version conflict resolution:
<project directDependencyStrategy="flexible">
<dependencies>
<!-- Strategy "flexible" applies: allows transitive deps to override this version -->
<dependency>
<groupId>com.example</groupId>
<artifactId>lib-a</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
</project>Or at the individual dependency level:
<dependency>
<groupId>com.example</groupId>
<artifactId>lib-b</artifactId>
<version>2.0</version>
<directDependencyStrategy>fixed</directDependencyStrategy> <!-- This version always wins -->
</dependency>Strategy Options
fixed(default): Direct dependency version is absolute; always wins version conflicts (current behavior)flexible: Allow the configured version resolver to apply its normal conflict resolution rules (e.g., highest version, nearest) even for direct dependencies, permitting transitive dependencies to override this direct dependency
Configuration
Strategy default can be controlled at multiple levels (lowest precedence to highest):
-
Maven Configuration:
maven.resolver.directDependencyStrategyproperty<properties> <maven.resolver.directDependencyStrategy>flexible</maven.resolver.directDependencyStrategy> </properties>
-
Project Level:
<project directDependencyStrategy="...">in POM -
Dependency Level:
<directDependencyStrategy>within individual<dependency>block
Consumer Behavior
When a direct dependency with non-default strategy is consumed as a transitive dependency:
- The
directDependencyStrategyattribute should be removed/not applied - Resolution follows normal transitive dependency rules using the active conflict resolver
- Consumer POM controls conflict resolution via their own direct dependency strategies
Safeguards
- Build Validation: Maven warns if unexpected downgrades occur during resolution when using
flexiblestrategy - Reactor Awareness: In-development versions in reactor builds always take precedence over deployed versions
- Explicit Override: Individual dependencies can override project-level strategy setting
Benefits
- Less Configuration: Developers need fewer
<dependencyManagement>entries - Explicit Intent: Clear declaration of whether a version is fixed or flexible
- Gradual Adoption: Backward compatible; defaults preserve current behavior
- Flexibility: Works with any conflict resolver implementation (highest, nearest, etc.)
- Platform Compatibility: Works alongside platform/BOM-based dependency management
Implementation Notes
- Extends
ConfigurableVersionSelectorto check strategy configuration before applying fixed direct dependency preference - Requires POM model updates to support
directDependencyStrategyattribute - Consumer POM transformations should strip
directDependencyStrategyattributes during publishing - Strategy resolution follows hierarchy: dependency-level > project-level > Maven property > hardcoded default
Activity
vlsi commentedon Nov 5, 2025
Having
directDependencyStrategyat dependency level looks strange.WDYT of something like:
dependencyVersionType:strict(select exactly this version),require(accept this or later),rejectdependencyVersionMode:strict(select exactly this version),require(accept this or later),rejectversionType:strict(select exactly this version),require(accept this or later),reject. It sounds better for<dependency>tag, however having a top level<project versionType="require">would look misleading.gnodet commentedon Nov 5, 2025
What would be the behaviour of the
rejectvalue ? Act likerequire, but fail if the version would change ?I think we could have a top level
dependencyVersionTypeonproject, and aversionTypeondependency.vlsi commentedon Nov 5, 2025
Ah. I missed the idea is to strip the attribute on publishing.
In that regard, it makes no sense to have
strict/reject/requireversion types.If the new attribute was kept when publishing, then transitive dependencies could bring requirements like
require 1.4 & reject [2.0,)below.My thought was as follows: sometime later the following cases might be helpful for both producers and consumers.
require 1.4 & reject [2.0,)In other words: "I require 1.4", however, if somebody requests 2.1, then the resolution should fail as I know there are incompatibilities.
strict 1.7In other words, "this works with 1.7 only"
In that regard,
versionTypecould control the way conflicts would be resolved.The initial implementation could be just
strict+require(orfixed+flexible).However, users would require a separate knob for "nearest vs highest" anyways, so their combinations would be as follows:
strict + nearest. We could issue warning and be done with itrmannibucau commentedon Nov 6, 2025
Open question:
aren't we looking for something overkilled? Shouldn't we just do an extension which enables to refine dependencies based on a custom criteria like in gradle but not support it by default to encourage consistency and reliability?
vlsi commentedon Nov 7, 2025
@gnodet , I have read the proposal again, and I do not see user story in it.
In other words, I do not see what would really change for the end-user if they flip the added
directDependencyStrategysetting. It would be great if you could clarify the end-to-end flowgnodet commentedon Nov 7, 2025
Given this is a breaking change, the idea is to let the user opt-in by changing using the maven configuration property in
.mvn/maven.propertiesor by setting thedirectDependencyStrategy="flexible"globally on theprojectelement. The flag could also be changed in~/.mvn/maven.propertiesor$MAVEN_HOME/conf/maven.propertiesto have a global effect for all projects.A possible (but optional) enhancement would be to make the flag available for each dependency as stated in the original proposal, but this would require much more change (first in the POM object model, then in the resolver), while the global flag should be implementable only in Maven.
Once the user switches to
flexible, the direct dependencies would be subject to conflict resolution. They are currently excluded, per the rule that, if the user explicitly specifies a version for a given dependency, that one should be honored. So basically direct dependencies always win during conflict resolution.So in your original
mvn-mediationuse case, we havelib-adepending onguice:5.0.1,lib-bdepending onguice:3.0,example-applicationdepending onlib-a,lib-bandguice:3.0. With the current behaviour,example-applicationhas a direct dependency onguice:3.0, so that version wins. If the user switches the project to useflexible, the direct dependency suddenly becomes subject to dependency resolution. ChangingdirectDependencyStrategyonly would not really change the output of the resolution, since the default version conflict resolution favours the dependencies closer to the top of the tree. However, if the user also changes the conflict resolution to usehighest, then, the direct dependency would be subject to higher policy conflict resolution and would be switched toguice:5.0.1.So this has to be coupled with selecting the highest version, else, there's not much point.
Given the flag only affects direct dependencies, this means that when a library is consumed by another project, the dependencies of the libraries become transitive dependencies, and the flag no longer apply. It thus becomes completely useless, which is a good thing, as it allows Maven to remove it from the consumer POM, since consumer POMs have a 4.0.0 model, and the flag could not be included anyway.
As a first step, I would not include the per-dependency flag, I'm not really it's worth it at the moment. If we don't want to change the model, we could also only use the maven property, just like we have with the
highestproperty for the dependency conflict resolver...gnodet commentedon Nov 7, 2025
Actually, I think this comes down to just a different VersionSelector, something like
flexible-highestinstead ofhighest. That may be sufficient.rmannibucau commentedon Nov 7, 2025
flexible-highestfor a common use case which is a downgrade?bom-transitivemaybe orunknownto be clear it will look random for end users maybevlsi commentedon Nov 7, 2025
@gnodet , do you mean activation of the property would automatically switch the resolution from "nearest to highest"?
Otherwise I do not see how
directDependencyStrategy=flexiblechanges anything.vlsi commentedon Nov 7, 2025
If you think of a global property that changes the resolution strategy, then what do you think of the following?
maven.propertiesto configure resolution mode (e.g.maven.dependency.resolution.mode):nearest(default to keep the current behavior),highestversionType=strict(default fornearest),require(default forhighest)In that case, there's no need in project-level
dependencyVersionType.gnodet commentedon Nov 14, 2025
@vlsi I'm thinking that we just need a new strategy in the resolver. We recently introduced
highestin the resolver, but that one does not take direct dependencies into account, following the unwritten rule that Maven does not override what the user explicitly stated.If we relax this rule, assuming the user means "I require at least this version of the lib", we can add a new
flexible(or whatever the name will be) strategy that will select the highest version for transitive and direct dependencies.Compared to my original proposal, it's much simpler, but does not allow to tune a given dependency explicitly.
The question is whether the added complexity (to configure for a given strategy) actually solves a real use case or not.
vlsi commentedon Nov 14, 2025
How about the following:
A new resolver goes with highest and treats default version declaration like
require(even directly declared ones)We add a new tag to
<dependency>so user could fine-tune if they really need to enforce a specific version (e.g. for testing or whatever purposes)Then all existing projects continue just like they are doing now:
nearestresolver, directly declared dependencies win.A switch to
highestresolver could be a single property in.mvn/...properties, and it could flip all deps (including direct) tohighestmode. That sounds easy to understand and config to me.It would solve the typical pain-points with
NoClassDefFoundErrorwhich are quite common for Maven users.A single configuration switch like "use highest dependencies" would have better chances of producing workable classpath than the current "nearest".
gnodet commentedon Nov 14, 2025
Yes, but this needs a model change, a change in the resolver API to add support for this new field, and the field to be taken into account the resolver strategies, either only highest, but maybe others, with unknown side effects.
Hence my proposal to just add a different strategy, or modify highest to just also affect direct dependencies. This would allow the switch with just a single property in
./mvn/...propertiesanyway.It really comes down to whether switching specific dependencies is a real use case or not. Given the problem is not know when the project is created, and mainly appears later when upgrading dependencies, I'm not sure why a user would choose to not follow the strategy for a given set of dependencies. If the user did switch to the new or updated
higheststrategy, and want to force a given dependency version, I think he can always choose to use a closed range in order to enforce this version. Though if the project is a library to be consumed by other projects, this will bring the constraints down to the consumer.desruisseaux commentedon Nov 14, 2025
I would like to mention another scenario to keep in mind. I have not yet explored how the
jar --hash-moduleoption work. But from this example we can get (output ofjar --describe-module):Therefore, we may need to implement in the future a strategy that requires the SHA-256 hash of modular JAR files to match. I do not yet understand well in which direction those hashes work, I just wanted to mention that we may have a use case for this kind of scenario.
vlsi commentedon Nov 14, 2025
In my experience, it is rarely used, however, it is useful.
For instance, if a newer release brings a bug in one of the old methods, users might want to forcefully downgrade even if one of transitive deps specifies a newer version (e.g. they applied Dependabot PR and do not rely on newer APIs).
Here's such a case in Gradle documentation: https://docs.gradle.org/current/userguide/how_to_prevent_accidental_dependency_upgrades.html#why_prevent_accidental_dependency_upgrades
I agree. For libraries, only ranges for
strictmake sense, and I believe for libraries,strictis not really needed in the first release.For example, GitHub search shows only a few usages of
strictlyin all Apache build scripts: https://github.com/search?q=org%3Aapache+strictly+path%3Agradle&type=codeIt might be, it would be good enough for the end users if the first release contains just a configuration option to flip between nearest vs highest (including direct ones).
It relates to dependency verification scenario which is orthogonal to resolution strategy.
desruisseaux commentedon Nov 14, 2025
We could create a strategy which, instead of saying "choose higher version", said "choose the version with matching SHA 256". I do not said that we need to do that, just mentioning as a possibility.
rmannibucau commentedon Nov 14, 2025
Guys, can we step back please? I totally understand this is trivial to add a new strategy which can do whatever we do want but how do you converge in the consumer pom since we do not flatten without transitive deps (explicit dep list)?
Is it ok to expose a tree which is not the one you did use to build and validate your project (not IMHO)?
vlsi commentedon Nov 14, 2025
I am afraid the selection based on hash is impractical for Java ecosystem at the moment:
How would you find which one corresponds to hash
12ccc61c573aed8a193f9727dc33795eba29d7c5?Maven repository layout is not designed to fetch artifacts by their hash.
commons-io?desruisseaux commentedon Nov 14, 2025
If the dependency tree contains two versions of
commons-io(for example), the hash is computed for those two versions from the JAR files cached in the local.m2repository.I know, but we don't need to search by hash. We only need to compute the hash of files in conflict after they have been downloaded.
The hash prevails if this is what the user requested. It would not be the default, only a strategy that users can select if this what they want.
vlsi commentedon Nov 14, 2025
Got your point. That might work.
However, can we please park the hash suggestion unless a compelling use case appears?
I do not think there are other dependency engines that implement such an approach, thus pioneering this in Maven would uncover many pitfalls.
desruisseaux commentedon Nov 14, 2025
Sure, no problem with that. I raised that point only for emphasising the value of a pluggable resolution strategy.
rmannibucau commentedon Nov 14, 2025
@cstamas doesn't the jpms module hash thing overlaps with what you did about CI build harnessing so could converge?
gnodet commentedon Nov 14, 2025
That's exactly what already happens when you consume a jar as a dependency. The resolver may (already) choose to bump to a more recent version. The only difference is that it would now be allowed with a direct dependency because a transitive Dep requires a higher version.
rmannibucau commentedon Nov 14, 2025
No, before the rule was unified in the build and mainly (even if tunable, nobody was using it almost) accross all projects, so all resolutions were well understood and known. Now the resolution of a 3rd party transitive deps will not more be aligned on the project which produces it view which is an issue IMHO so until we can flatten back the consumer pom we can have weird cases. Resolvable but useless headache for end users IMHO for an use case which is really rare since you never need that to resolve properly your dependencies (it is just an enhancement for comfort when you do not like defaults).
gnodet commentedon Dec 2, 2025
If we keep this as a property to be specified as a maven user property (cli or config file), this information won't be available to consumers, so this can only affect build time behavior.
rmannibucau commentedon Dec 2, 2025
it is my concern, your project view is different than your consumer view, not sure it is that sane since you can ask all your consumer to do work to be aligned with your expectations
Bukama commentedon Dec 18, 2025
I've read the discussion on ML and this ticket. Another point I see, which might introduce a third option, are version ranges. For example
strictcould disallow them by default even without an explicit enforcer rule (of which many users are not aware of). Next to thefixedandflexiblefor the other use cases.Related: