Avoiding major/minor version conflicts with Java/Maven dependencies - java

Is it a good idea to publish a Maven project using this kind of scheme:
<groupId>com.oresoftware</groupId>
<artifactId>async.1</artifactId>
<groupId>com.oresoftware</groupId>
<artifactId>async.2</artifactId>
<groupId>com.oresoftware</groupId>
<artifactId>async.3</artifactId>
where these represent major releases of the projects? Would this not be an effective way to create a different namespace so that different dependencies in the tree could depend on different versions of this library? Does anyone do this or is this a bad practice?
I was even thinking about namespacing them by minor version too:
<groupId>com.oresoftware</groupId>
<artifactId>async.1.1</artifactId>
<groupId>com.oresoftware</groupId>
<artifactId>async.1.2</artifactId>
<groupId>com.oresoftware</groupId>
<artifactId>async.1.3</artifactId>
Update, supposedly this is what Apache Commons did between version 3 and 4, here are two different imports:
version 3:
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.2</version>
</dependency>
version 4:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
So my question is - to have a different package name or namespace, is it sufficient to use a different artifactId or do we also need a different groupId?

No, it is only a different namespace if the package name of the classes are also different.
Since they are likely not, you're just setting yourself up for more trouble, since you can now add more than one version of the classes. Given that the classpath order is usually unordered (e.g. in a webapp), it'll be arbitrary which one you'll actually see at compile-time and/or runtime, causing unpredictable behavior.
Now, if you also rename the packages, like what Apache Commons Collections did between version 3.x and 4.x, then you should rename the maven group/artifact too, so you can have both on the classpath.
Otherwise you should instead try to make the code backwards compatible, so a newer version of the library can be used without causing issues.

You can do this, as Andreas talked about in the other answer. But it is rarely done.
I thought about concepts like these myself, and I guess the main reason is that upgrading from version n to n+1 becomes much harder. You need to go through all the code and replace the imports. Furthermore, if you use objects from your library in interfaces, you bind your method parameters to a specific version of the library (through the imports) and make interoperability harder.
Often, upgrading from n to n+1 does not mean that everything breaks, but maybe just a little bit, so going through all the code may be harder than accepting the problems of the usual version scheme.

If you do that, then you must also change the package of your classes; otherwise you will cause even more conflicts to your clients. Looking at the commons-collections jars; the package for version3 is org\apache\commons\collections\.. and in version 4, they changed the package to org\apache\commons\collections4\.. .
So that a client can import both libraries and use both of them at runtime, and still have no conflicts.
So to answer your question, it is not enough to change the groupId/artifactId. Either you bump the version (and leave groupId/artifactId unchanged); so that maven/gradle will select eg. version 1, if version 1 and 2 are imported. But if you change the artifactId, you circumvent the conflict resolution mechanism based on version-s; and a client could import both. Then in order to not have conflicts, you must change the package name (for all classes included in you jar).

Alright so here is the plan
Keep the groupId the same
Change the artifactId from async1 to async2 to async3 for each major version change and the version field would be 1.x.x and then 2.x.x and then 3.x.x
in the codebase the directory structure needs to change from src/main/java/com/oresoftware/async1 to src/main/java/com/oresoftware/async2 to src/main/java/com/oresoftware/async3
and that's it.. AFAICT

Related

same accessible package from two different modules in Lucene leading Eclipse to raise an error [duplicate]

I'm developing on a Maven project (branch platform-bom_brussels-sr7) in Eclipse. When I recently tried switching the Java Build Path for the project to JDK 10, Eclipse build can no longer find classes such as javax.xml.xpath.XPath, org.w3c.dom.Document, or org.xml.sax.SAXException. It seems only XML related classes are impacted, mostly from the Maven dependency xml-apis-1.4.01.
Trying a Maven build from Eclipse works without errors. Ctrl-LeftClick on one of the supposedly missing classes finds the class and opens it in the Eclipse editor. It seems only the Eclipse build is impacted.
I tried several things, but none helped. I tried:
Project Clean
Different Eclipse Versions: Oxygen and Photon.
Running Eclipse itself with JDK 8 and JDK 10.
Changing Compiler Compliance level for the project. It builds with compliance level 8 and 10 under JDK 8 build path and fails for both with JDK 10 in build path.
I assume that the project being migrated from Java 1.8 still has no module-info.java. This implies you are compiling code in the "unnamed module".
Code in the unnamed module "reads" all observable named and unnamed modules, in particular it reads module "java.xml" from the JRE System Library. This module exports package like java.xml.xpath.
Additionally, you have xml-apis.java on the classpath, which contributes another set of packages of the same names (java.xml.xpath and friends). These are said to be associated to the unnamed module, like your own code.
This situation violates the requirement of "unique visibility" as defined in JLS §7.4.3 (last paragraph). In particular every qualified type name Q.Id (JSL §6.5.5.2) requires that its prefix Q is a uniquely visible package (I'm disregarding the case of nested types for simplicity). Ergo: the program is illegal and must be rejected by compilers.
This leaves us with one question and two solutions:
(1) Question: Why is javac accepting the program?
(2) Solution: If you add module-info.java to your project, you can control via requires which module your project reads, either requires java.xml; or requires xml.apis; (where "xml.apis" is the automatic module name of "xml-apis-1.4.01.jar).
(3) Solution: Short of turning your project into a module, you can still avoid the conflict by excluding java.xml from the set of observable modules. On the command line this would be done using --limit-modules. The equivalent in Eclipse is the "Modularity Details" dialog, see also the JDT 4.8 New&Noteworthy (look for Contents tab). Since java.xml is implicitly required via a lot of other default-observable modules, it may be a good idea to push everything except for java.base from right ("Explicitly included modules") to left ("Available modules") (and selectively re-add those modules that your project needs).
PS: Eclipse still doesn't provide an ideal error message, instead of "cannot be resolved" it should actually say: "The package javax.xml.xpath is accessible from more than one module: javax.xml, <unnamed>.
PPS: Also weird: how come that changing the order between JRE and a jar on the classpath (such ordering is not a concept supported by javac nor JEP 261) changes the behavior of the compiler.
EDITs:
Alex Buckley confirmed that the given situation is illegal, despite what javac says. Bug against javac has been raised as JDK-8215739. This bug has been acknowledged months before the release of Java 12. As of 2019-06 it has been decided that also Java 13 will ship without a fix. Similarly for Java 14. The bug was temporarily scheduled for Java 15, but this plan has been dropped on 2020-04-20.
Eclipse error message has been improved to mention the real problem.
In Eclipse 2019-06 the UI used for Solution (3) has been revamped. Up-to-date documentation can be found in the online help.
As of 2022-12 there's yet another perspective on this issue as described in my other answer. It doesn't invalidate what's said here, but let's things appear in a different light.
In my case the problem was that xercesImpl : 2.10.0 was a (transient) dependency. This jar bundles org.w3c.dom.html.HTMLDOMImplementation.
As far as I understand the org.w3c.dom package then becomes available from two modules, causing the build to fail.
In case one of the dependencies (direct or transient) has classes in one of the 25 packages exported by the java.xml module your build will fail.
Excluding xercesImpl (and also the offenders listed below) in Maven solved the issue for me:
<dependency>
<groupId>xyz</groupId>
<artifactId>xyz</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
</exclusion>
<exclusion>
<groupId>xml-apis</groupId>
<artifactId>xml-apis</artifactId>
</exclusion>
<exclusion>
...
</exclusion>
</exclusions>
</dependency>
Thanks to Rune Flobakk for giving the hint here: https://bugs.eclipse.org/bugs/show_bug.cgi?id=536928#c73
Other offenders:
batik-ext : 1.9 (bundles org.w3c.dom.Window)
xom : 1.2.5 (bundles org.w3c.dom.UserDataHandler)
stax-api : 1.0.2 (bundles javax.xml.stream.EventFilter)
xml-apis : 1.4.01 (bundles org.w3c.dom.Document)
xml-beans : 2.3.0 (bundles org.w3c.dom.TypeInfo)
While the accepted answer (by myself) is still correct, a further twist of the story was recently brought to my attention:
The original intention may have been to actually support the situation at hand.
See this quote in the original design document "The State of the Module System" (SotMS):
If a package is defined in both a named module and the unnamed module then the package in the unnamed module is ignored.
That document is dated 2016/3/8 08:18, and already at that time was marked "This document is slightly out of date". Moreover, it is not legally binding for any implementation. Still that document has some relevance since what's quoted above is precisely what javac appears to implement (and still implements many years after JDK-8215739 was filed).
IOW, the conflict is not so much a conflict between 1st and 2nd implementation, but a conflict even within Oracle, so it seems. 2 Votes for supporting the situation (SotMS and javac) and only one vote for disallowing (JLS).
Since Eclipse committers are not inclined to resolve this conflict within Oracle, the recent 2022-12 release of Eclipse has a new compiler option: by adding the following line to a project's .settings/org.eclipse.jdt.core.prefs, a user may opt to ignore JLS in this regard:
org.eclipse.jdt.core.compiler.ignoreUnnamedModuleForSplitPackage=ENABLED
This option puts the decision into the user's hands: do they want JLS-semantics or SotMS/javac semantics (in this particular issue)? Still we were not quite ready to provide a UI option for it, to avoid that users made this choice thoughtlessly, without the background information as provided here.
Personally, I'm not particularly happy about this situation, as it aggravates the fact that Java is not one, but several languages.
This seems to have been reported as Eclipse Bug 536928. Maybe if everyone were to go vote on it it would get them to raise the priority.
What happens here is you have a wildcard import like import org.w3c.dom.*, stating you want to import all classes from package org.w3c.dom. Now, if there's at least one class in org.w3c.dom provided by a second source, Java must not start (as pointed out here).
(By the way, the message "... cannot be resolved" is replaced by a more accurate error message "The package org.w3c.dom is accessible from more than one module: <unnamed>, java.xml" in more recent Eclipse versions, see this merged change request by Stephan Herrmann.)
To resolve this problem
Open the "Open Type" dialog (Ctrl+Shift+T).
Enter the complete import, so org.w3c.dom.* or org.w3c.dom..
Check the entire list for multiple sources. All entries here should contain only something like "jdk-11-...".
Gather all JARs that contain classes you have multiple sources for.
Open the "Dependency Hirarchy" tab from pom.xml.
Search for the JAR file.
Add an exlusion (right click or edit the pom.xml manually).
Example
I had this findbugs dependency in my pom.xml:
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>findbugs</artifactId>
<version>${findbugs.version}</version>
</dependency>
Findbugs has two dependencies that need to be excluded:
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>findbugs</artifactId>
<version>${findbugs.version}</version>
<exclusion>
<groupId>xml-apis</groupId>
<artifactId>xml-apis</artifactId>
</exclusion>
<exclusion>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
</exclusion>
</dependency>
While Stephan Herrmann's answer is the correct one, I'll post my error and how I got it solved if it can help others. I had the error The package javax.xml.namespace is accessible from more than one module: <unnamed>, java.xml and after inspecting the class with the error, it was the javax.xml.namespace.QName import that was complaining. With the "Open Type" dialog, I found out that it was pulled from stax-api through eureka client. This solved it for me :
<exclusion>
<groupId>stax</groupId>
<artifactId>stax-api</artifactId>
</exclusion>
Have seen something very similar under Eclipse 4.8.0 and JDK 10. E.g.
import org.w3c.dom.Element;
was failing to compile in Eclipse with: The import org.w3c.dom.Element cannot be resolved
Even so, pressing F3 (Open Declaration) on that import, Eclipse was able to open the interface definition - in this case under xml-apis-1.4.01.jar.
Meanwhile, builds from Maven direct were working fine.
In this case the fix was to remove this dependency from the pom.xml:
<dependency>
<groupId>xml-apis</groupId>
<artifactId>xml-apis</artifactId>
<version>1.4.01</version>
</dependency>
Then the compile errors in Eclipse melted away. Following F3 again showed the Element interface - now under the java.xml module, under the JRE System Library under the project. Also the Maven build remained fine.
This feels like a problem with Eclipse resolving a class that it finds in both a JDK module and dependent .jar file.
Interestingly, in a separate environment, this time under Eclipse 4.9.0 and JDK 11, all is fine, with or without the xml-apis:1.4.01 dependency.
This is more of a work-around, but from my experience it can be resolved by going to the "Java Build Path", the "Order and Export" tab, and sending the "Maven Dependencies" to the bottom (so it's below the "JRE System Library").
Thanks for this clue. I was having trouble identifying where the conflicting reference was coming from for org.w3c.dom.Document. Found it easily in Eclipse 2020-12 this way: Selected org.w3c.dom.Document within the import statement that Eclipse flagged, right-click and choose Open Type Hierarchy, in the Type Hierarchy dialog right click Document at the top and choose Implementors > Workspace to reveal all the JARs in all projects in the workspace which are bringing in org.w3c.dom.Document (or whatever type you have selected that is accessible from more than one module – MikeOnline yesterday
following the directions above from one of the earlier posts helped us solve our issue.
what we did was replace Document with GenericDocument and Element with GenericElement from batik - and compile errors are gone - now we just have to test to make sure the implementation matches what we had under java 8. Thanks MikeOnline
jdk 9+ brought in changes related to project jigsaw. JDK was broken down into various modules and some modules, javaee, jaxb and xml related, are no more loaded by default. You should add these to your maven build directly, instead of expecting them to be in jre classpath. see this SO question

partion same dependency name with muiltiple versions in the same project

I have a Spring module having:
1) depedency org.hibernate-validator 6...
2) transitive depedency org.hibernate-validator 5...
3) uber transitive depedency hardFileCoded in fat jar(gwt-user) of vaadin dependency with org.hibernate-validator <6
They seem uncompatible to interchange.
The problem is - they do not conflict and raise ex.
But in compiletime(it follows bad artifact till successful remake) they mess randomly(?).
And wrong version(?) is used for retriving validation message error text.
Resulting in bad print because versions <=5 does not have javax.validation.constraints.NotBlank.message:
Object: ***, error: {javax.validation.constraints.NotBlank.message}
instead of correct:
Object: ***, error: must not be blank
I can't really remove something etc.
Need somehow to distinguish them and use appropriate versions in places.
First of all limit that fat jar validator to never go out of vaadin =)
Thanks a lot in advance for any directions to dig.
As always you should look for a normal healthy dependency
where you can <exclude> (maven) some transitives and include them explicitly with the right version
also check this dependency is really needed
in rare extra cases see classloaders, so you can load same classes of different versions for a different consumers
In my concrete case found that fat jar unused and free to delete =)
Thanks for recommendations.

Maven: Identify the correct dependency from given code

What is the best way to find the right dependency for a used class that are part of the maven-online-repository?
As far I see it is this approach:
lookup the import (e.g. org.whatever.X;) from your code at the maven-repository online (search.maven.org).
Pick one of the result list and include it in the dependency section of the POM.
Hope the chosen version and artifact of the dependency matches your requirements (compiling, runtime). If not try another artifact or version.
I'd like to share my way of doing it. What do you mean by "finding the ... for a used class that are part of the ..."? Do you mean that the dependancy is already used in somewhere else, or that you only know the package name that you may need?
I would first check which version I need for the current project.
If I'm working on a team project and someone has used the dependency in somewhere else, I would check their pom (to ensure we are using the same dependency).
Then I would look up the dependency in Maven repo and include it in my pom.
Hope this helps.
Essentially, yes this is what you have to do to obtain libraries/modules for your project.
Something that's helped me out though with this specific problem: versioning. You can set the versions you need for each of your dependencies with <properties> -> <gson.version>2.8.1</gson.version> (for example). That way, you can guarantee that your build matches with the reqs of the class or type of code you're trying to implement.
Maven doc ref: https://maven.apache.org/pom.html#Properties

Can Gradle help solve jar hell in any way?

Java 8 here.
Say there is an old version of the widget libray, with Maven coordinates widgetmakers:widget:1.0.4, that has a class defined in it like so:
public class Widget {
private String meow;
// constructor, getters, setters, etc.
}
Years pass. The maintainers of this widget library decide that a Widget should never meow, rather, that it should in fact bark. And so a new release is made, with Maven coordinates widgetmakers:widget:2.0.0 and with Widget looking like:
public class Widget {
private Bark bark;
// constructor, getters, setters, etc.
}
So now I go to build my app, myapp. And, wanting to use the latest stable versions of all my dependencies, I declare my dependencies like so (inside of build.gradle):
dependencies {
compile (
,'org.slf4j:slf4j-api:1.7.20'
,'org.slf4j:slf4j-simple:1.7.20'
,'bupo:fizzbuzz:3.7.14'
,'commons-cli:commons-cli:1.2'
,'widgetmakers:widget:2.0.0'
)
}
Now let's say that this (fictional) fizzbuzz library has always depended on a 1.x version of the widget library, where Widget would meow.
So now, I'm specifying 2 versions of widget on my compile classpath:
widgetmakers:widget:1.0.4 which is pulled in by the fizzbuzz library, as a dependency of it; and
widgetmakers:widget:2.0.0 which I am referencing directly
So obviously, depending on which version of Widget gets classloaded first, we will either have a Widget#meow or a Widget#bark.
Does Gradle provide any facilities for helping me out here? Is there any way to pull in multiple versions of the same class, and configure fizzbuzz classes to use the old version of Widget, and my classes to use the new version? If not, the only solutions I can think of are:
I might be able to accomplish some kind of shading- and/or fatjar-based soltuion, where perhaps I pull in all my dependencies as packages under myapp/bin and then give them different version-prefixes. Admittedly I don't see a clear solution here, but am sure something is feasible (yet totally hacky/nasty). Or...
Carefully inspect my entire dependency graph and just make sure that all of my transitive dependencies don't conflict with each other. In this case for me, this means either submitting a pull-request to the fizzbuzz maintainers to upgrade it to the latest widget version, or, sadly, downgrading myapp to use the older widget version.
But Gradle (so far) has been magic for me. So I ask: is there any Gradle magic that can avail me here?
Don't know the specifics of Gradle, as I'm a Maven person, but this is more generic anyway. You basically have two options (and both are hacky):
ClassLoader magic. Somehow, you need to convince your build system to load two versions of the library (good luck with that), then at runtime, load the classes that use the old version with a ClassLoader that has the old version. I have done this, but it's a pain. (Tools like OSGI may take away some of this pain)
Package shading. Repackage the library A that uses the old version of library B, so that B is actually inside A, but with a B-specific package prefix. This is common practice, e.g. Spring ships its own version of asm. On the Maven side, the maven-shade-plugin does this, there probably is a Gradle equivalent. Or you can use ProGuard, the 800 pound gorilla of Jar manipulation.
Gradle will only set up the classpath with your dependencies, it doesn't provide its own runtime to encapsulate dependencies and its transitive dependencies. The version active at runtime will be the one according to the classloading rules, which I believe is the first jar in the classpath order to contain the class. OSGI provides runtime that can deal with situations like this and so will the upcoming module system.
EDIT: Bjorn is right in that it will try to resolve conflicts in different versions; it'll compile the classpath based on its strategies, so the order you put your dependencies in the file doesn't matter. However you still only get one class per classname, it won't resolve OP's issue
If you have different versions of a library with otherwise equal coordinates, Gradles conflict resolution mechanism comes into play.
The default resolution strategy is to use the newest requested version of the library. You will not get multiple versions of the same library in your dependendcy graph.
If you really need different versions of the same library at runtime you would have to either do some ClassLoader magic which definitely is possible or do some shading for one of the libraries or both.
Regarding conflict resolution, Gradle has built-in the newest strategy that is default and a fail strategy that fails if different versions are in the dependency graph and you have to explicitly resolve version conflicts in your build files.
Worse case is when the same class appears in multiple jars. This is more insidious - look at the metrics jars from Codahale and Dropwizard with incompatible versions of the same class in the two jars.
The gradle classpath-hell plugin can detect this horror.

android javax.xml.bind gson and #XmlSchemaType

I have an xsd provided by a client I can't change. I'm using jaxb to generate the classes. The resulting class has an annotation like:
#XmlSchemaType(name = "dateTime")
protected XMLGregorianCalendar expires;
I try to turn this into json using GSON and I get a classnotfoundexception for javax.xml.bind.XmlSchemaType. I add the dependency for javax.xml.bind in maven pom.xml:
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.xml.bind</artifactId>
<version>3.0-b54</version>
<scope>compile</scope>
</dependency>
Then I get this crazy error:
Attempt to include a core class (java.* or javax.*) in something other
than a core library. It is likely that you have attempted to include
in an application the core library (or a part thereof) from a desktop
virtual machine. This will most assuredly not work. At a minimum, it
jeopardizes the compatibility of your app with future versions of the
platform. It is also often of questionable legality.
If you really intend to build a core library -- which is only
appropriate as part of creating a full virtual machine distribution,
as opposed to compiling an application -- then use the
"--core-library" option to suppress this error message.
If you go ahead and use "--core-library" but are in fact building an
application, then be forewarned that your application will still fail
to build or run, at some point. Please be prepared for angry customers
who find, for example, that your application ceases to function once
they upgrade their operating system. You will be to blame for this
problem.
If you are legitimately using some code that happens to be in a core
package, then the easiest safe alternative you have is to repackage
that code. That is, move the classes in question into your own package
namespace. This means that they will never be in conflict with core
system classes. If you find that you cannot do this, then that is an
indication that the path you are on will ultimately lead to pain,
suffering, grief, and lamentation.
!!!
I either need to skip the annotation, tell jaxb not to generate themor gson to ignore them or import the dependency correctly.
<scope>provided</scope>
fixed me up - i wasn't aware that everything under javax.* is provided by android.

Categories

Resources