Hi

Le dim. 12 oct. 2025 à 16:52, Vladimir Sitnikov <[email protected]>
a écrit :

> >1. commons-compress 2 must not be compatible with v1 by commons rule (did
> >we miss it? the rule is to change the package to "2")
>
> Romain, It sounds like you’re referring to an Apache Commons convention
> (e.g., commons-lang -> commons-lang3, package rename to allow
> side-by-side).
>
> That can be useful for incompatible redesigns, but for a change like “keep
> the same APIs, just split one JAR into multiple modules,”
> forcing package renames places a significant burden on consumers.
>
> More broadly, preserving backward compatibility lets users upgrade without
> code churn.
> FWIW, semver.org does not require “v2 must be incompatible with v1”
>

Agree all these statements, just uncommon for commons and unexpected for
end users.
As a side note we did it quite regularly in tomee around v1 and we always
managed to keep backward compat using an aggregator module (sometimes with
a fake jar to be 100% backward compat).
Long story short, there is no reason to do a v2 for that and if the v2 is
incompatible it must change packages and gav at commons so I must be
missing something there.


>
> >2. in your pom you can always exclude from a dep a transitive dep and
> enforce it explicitly
>
> Could you please clarify with a concrete example?
> If you mean adding exclusions everywhere and then adding an explicit
> dependency on commons-compress,
> that’s exactly the kind of manual fix I’m hoping to avoid.
>

Yes, and the good news is that you will never avoid it and your example of
commons-compress-tar is perfect, you cant replace commons-compress by
commons-compress-tar cause it is not equivalent.
This is also where the plugin idea to help handling such case (replace goal
in dependency plugin) cause we already do bytecode inspection in this
plugin so it would be safer even if not 100% to do it like that (mvn
dependency:replace -Dfrom=commons-compress -Dto=commons-compress-tar) than
your proposal which is as unsafe than having a pivot pom in your project
when you 100% know what you do (this is for ex what you do with hadoop-aws
when you do not want aws bundle but official aws deps - but at your own
risk).


>
> Here's the pom for the application:
>
> https://github.com/vlsi/jarsplit/blob/5e3fa1ecd3f26a2116aa83a92e85b69560a597f6/lib-user-maven4/pom.xml
>
> Consider a novice user who depends on both lib-uses-v1 and lib-uses-v2.
> Today Maven resolves commons-compress to 1.0.0, and they get a
> NoSuchMethodError.
> The recommended fix becomes: “exclude commons-compress (which you never
> used directly) from multiple places
> and then add it back explicitly at the top level; and later remember to
> remove the workaround once transitive dependencies stabilize.”
>
> I think the build tool can do better than that.
>

If you think about it, it has nothing to do with the build tool and the
build tool can't know what to prefer in such a case since artifacts are
different (would you replace commons-text with commons-math, this is
strictly equivalent for the build tool).

A better option would be to provide a lib-bom or something like that, but
still unrelated to the build tool.


>
> Here’s the kind of change I’m trying not to require users to hand-craft:
>
> --- a/lib-user-maven4/pom.xml
> +++ b/lib-user-maven4/pom.xml
> @@ -6,15 +6,32 @@
>      <artifactId>app</artifactId>
>      <version>1.0.0</version>
>      <dependencies>
> +        <dependency>
> +            <groupId>org.example</groupId>
> +            <artifactId>commons-compress</artifactId>
> +            <version>2.0.0</version>
> +        </dependency>
>          <dependency>
>              <groupId>org.example</groupId>
>              <artifactId>lib-uses-v1</artifactId>
>              <version>1.0.0</version>
> +            <exclusions>
> +                <exclusion>
> +                    <groupId>org.example</groupId>
> +                    <artifactId>commons-compress</artifactId>
> +                </exclusion>
> +            </exclusions>
>          </dependency>
>          <dependency>
>              <groupId>org.example</groupId>
>              <artifactId>lib-uses-v2</artifactId>
>              <version>2.0.0</version>
> +            <exclusions>
> +                <exclusion>
> +                    <groupId>org.example</groupId>
> +                    <artifactId>commons-compress</artifactId>
> +                </exclusion>
> +            </exclusions>
>          </dependency>
>      </dependencies>
>      <build>
>
> This is brittle and requires users to know internal transitive details they
> shouldn’t have to care about.
>

This is always the case IMHO, not knowing your transitive deps is a bad
solution in 100% of cases, the build tool must be friendly to transitivity
but user must always check what he does use.


>
> Note: the app does not use commons-compress, nor does it know there's
> commons-compress somewhere down the road.
>

Do you care you have a CVE in commons-compress? If not maybe you should and
if you do then the app does know it.
The point is transitivity doesn't mean lack of knowledge.


>
> Yes, I could add a top-level dependencyManagement:
>
>     <dependencyManagement>
>         <dependencies>
>             <dependency>
>                 <groupId>org.example</groupId>
>                 <artifactId>commons-compress</artifactId>
>                 <version>2.0.0</version>
>             </dependency>
>         </dependencies>
>     </dependencyManagement>
>
> …but that’s precisely the point of my original request: the same
> dependencyManagement already exists in commons-compress-core:2.0.0,
> and I’d like Maven to honor it transitively so end users don’t need to
> duplicate it in their root POMs
> (which may not even “see” all the relevant transitive edges).
>

Ok, now i have my-lib which has a depMgt with commons-compress:1.0, what
does maven? prefer the first encountered one (whatever transversal is used)
-> back to your original issue.
I reallu think you should just ensure that when you add a dep you know what
you add.


>
> >3. if 2 is bothering the common trick is to do a module with packaging=pom
> >doing the exclude+explicit dep and use this instead of the 3rd party
> ...
>
> If by this you mean creating a wrapper module (a BOM or a “starter”) that
> either (a) manages versions or (b) carries curated dependencies plus
> exclusions,
> then yes, that’s a known workaround.
> It works, but it pushes complexity onto every consumer to adopt yet another
> indirection layer.
>

s/consumer/integrator/ and I think it is by design whatever the build tool
does offer.
the only topic which can be enhanced for me is really helping to detect and
do it at scale but not the fact to do it silently which would break more
often than it would help as explained.


>
> My goal is to avoid forcing all library users to create or adopt wrapper
> modules just to get a safe, convergent outcome.
>

This is not the case in your example, it is only the case because you have
conflicting libraries and not using OSGi or alike.


>
> I would much prefer Maven to honor the dependencyManagement present in
> transitive POMs (e.g., commons-compress:2.0.0 managing its split modules).
> That would let the ecosystem migrate from single-JAR to multi-JAR without
> package renames and without end-user boilerplate
>

As explained it doesn't solve your issue exactly for the same reason you
have it, you just moved your issue one step further.


>
> >4. maven enforcer or any other plugin to prevent
>
> the usage of v1 (or v2 aggregator module) anywhere
> Enforcer is helpful for guardrails, agreed. I still want Maven to resolve
> to 2.0.0 automatically in the simple case where an application depends
> on two libraries that require different major lines of the same component.
>
> Enforcer can catch regressions; it shouldn’t be the primary mechanism to
> coax Maven into a workable resolution.


> Vladimir
>

Reply via email to