1

Occasionally, the question of using Maven Version Ranges to specify dependencies arises in our projects.

The extended "syllogism" goes something like this:

  • App X has Internal (our organization's) Dependency Y
  • Dependency Y has an external Dependency Z (e.g. g:a of org.apache.httpcomponents:httpclient)
  • The provider of Dependency Z is very good about providing backward compatibility within major.minor versions numbers (eg. for httpclient, 4.5.1 to 4.5.2 can reliably assumed to contain only bug fixes and no API changes)
  • If App X wants to take advantage of an update to Dependency Z, it shouldn't require a new build of Dependency Y.
  • In fact, we also have dependency Y1, Y2, Y3, etc. which all provide similar features (clients of different Web Services) and rely on the same major.minor version of httpclient and are indifferent (aside from any relevant bug fixes) to the .incremental version.

Therefore, Dependency Y, Y1, Y2, etc. can have their version of Dependency Z specified as a range (e.g. httpclient:[4.5., 4.6) in order to say "any version 4.5.x of httpclient will suffice") allowing App X to "upgrade" to a new incremental version of Dependency Z by specifying a managed version of that dependency (i.e. internally fix the version itself to something within the provided range identified by Dependency Y)?

The alternative seems to be that Dependency Y (and related) continue to specify a specific version of Dependency Z (latest known, but could be slightly out of date) as a un-ranged <version>4.5.inc</version> so that when Dependency Z provides a newer version, App X has the following options:

  1. Manage the version of Dependency Z internally (i.e. force the latest version to override Dependency Y's preferred version)
  2. Update Dependency Y's stated version of Dependency Z, rebuild, do the same of all Y1, Y2, Y3, etc. and then update App X's versions of Dependency Y, Y1, Y2, etc.

But .. the "I'm treating Dependency Z as flexible in practice, but unwilling to state that in the pom.xml of Dependency Y" of #1 seems dishonest.

The "trickle-down upgrade" from #2 is the current practice and is a pain point. It seems to provide little value since there are no internal changes to code of the Dependency Y family, just the updated dependency.

Observations on the above:

  1. The proposal of the "syllogism" seems to violate the "Don't Use Version Ranges" school of thought. While App X has "build reproducibility", dependency Y does not necessarily... although with a limited range, it may be acceptable.
  2. Alternative-1 seems to be advocated by the "Don't Use Version Ranges" posts that I came across.
  3. Is alternative-2 (current practice) simply a necessary evil to ensure "reproducibility" in both App X and Dependency Y?

So, the key question is: is there an option or alternative which I'm overlooking which avoids feeling this tension between "trickle down upgrades" and "build reproducibility"?

Ideally, it would be something which explicitly states: "Build Dependency Y with version g:a:maj.min.inc but leave transitivity ambiguous and accept any g:a:maj.min.? version downstream".

Jacob Zwiers
  • 1,092
  • 1
  • 13
  • 33
  • That's a good question... and as you say, there are two approaches, but I don't see how you could merge them into a 3rd one. The dependency used in the build, and the one that'll be the transitive dependency for consumers are the same... You could leave out the Z dependency from Y, and X will entirely rely on Z, but that's not a great practice either... – Tunaki Nov 28 '16 at 16:34

1 Answers1

1

I can see two options...

If Y's are only used in the context of X or similar, you could move the dependency to Z up a level - define it as provided in Y (with version range), and a first level dependency in X with a version range. This way dependency in Y will in fact say what you want it to (newest version in that range please), and it will always get satisfied with the newest version during each build of X.

If that's not the case Y's will get built with some version of Z and it might not be newest now. The only choice now if you do want the newest version is to override (or trickle down updates) as you said. It's not that dishonest: range signifier in Y's .pom file will be a hint to X implementator that it's dependency in fact might want to be overriden should the need arise.

Deltharis
  • 2,320
  • 1
  • 18
  • 29
  • Based on my experiences (described here: http://stackoverflow.com/questions/40851764/maven-version-range-to-exclude-next-minor-version), I no longer consider ranges an option and have taken a long draught of the 'Maven Version Ranges are Evil' Kool-Aid. I haven't decided yet whether I'll keep doing the "trickle down" or only update Y's stated version number of Z if there's a fix to Z known to address a pertinent issue. – Jacob Zwiers Nov 30 '16 at 17:04
  • Using `provided` is problematic. I quick trial got me into a situation where where Project Y depends on Z (marked as `provided`). If Project X depends on Y, but not directly on Z as well, then Project X needs to explicitly state Project Z as a dependency or it won't compile. That removes the conveniences of "transitive" dependencies by requiring Z to be specified in places where it otherwise does not need to be. Trickle-down or letting projects override is the way to go. – Jacob Zwiers Dec 01 '16 at 17:30