How does maven handle same jar with different group-id - java

I am using different libraries that reference the same library in different versions with different group-ids (relocated). So maven assumes that those are different libraries and puts them both in classpath.
Nevertheless the package-names have not changed, so the same class with the same package-name exist in maven.
This is e.g. the case with org.bouncycastle:bcmail-jdk14-1.38 vs. org.bouncycastle:bcmail-jdk14-138. But since both versions are identical I don't expect trouble.
But with javax.xml.stream:stax-api-1.0-2 vs. stax:stax-api-1.0.1 we have no identical bytecode, so the same class with different content will be in the classpath.
Am I right - and if so, can maven warn me and prevent such problems?

You can use the enforcer rule
https://www.mojohaus.org/extra-enforcer-rules/banDuplicateClasses.html
that allows you to break the build if a class name appears in more than one jar.

No, for Maven those are different artifacts and you can get into trouble. Having multiple versions of the same class on the class path is not a good idea.

Related

Does Java classpath different order give "No Method found error"

I have a Scala Akka Application which connects to HBase (currently CDP earlier HDP) deployed on rancher; Never faced any trouble when connecting to HDP hbase; Since recent HDP to CDP change, with the same image we are getting no method found on one of the dependency's class in one of the container, where as another container of same image connects to hbase properly.; even though the jar exists in the same image and classpath also.
one of the noticeable difference is change in the order of classpath.
Does change in the classpath order will effect the jars availability.
Does java libraries/classes would load in different order when they would hit a faster CPU cycle at startup.
What could be the reason for such "no class method found".
It certainly can, if the same class file is present in different classpath entries. For example, if your classpath is: java -cp a.jar:b.jar com.foo.App, and:
a.jar:
pkg/SomeClass.class
b.jar:
pkg/SomeClass.class
Then this can happen - usually because one of the jars on your classpath is an older version than the other, or the same but more complicated: one of the jars of your classpath contains a whole heap of different libraries all squished together and one of those components is an older version.
There are some basic hygiene rules to observe:
Don't squish jars together. If you have 500 deps, put 500 entries on your classpath. We have tools to manage this stuff, use them. Don't make striped jars, uber jars, etc.
Use dependency trackers to check if there are version difference in your dependency chain. If your app depends on, say, 'hibernate' and 'jersey', and they both depend on google's guava libraries, but hibernate imports v26 and jersey imports v29, that's problematic. Be aware of it and ensure that you explicitly decide which version ends up making it. Presumably, you'd want to explicitly pick v29 and perhaps check that hibernate also runs on v29*. If it doesn't, you have bigger problems. They are fixable (with modular classloaders), but not easily.
*) Neither hibernate nor jersey actually depend on guava, I'm just using them as hypothetical examples.
For example, if you use maven, check out the enforcer plugin. (groupId: org.apache.maven.plugins, artifactId: maven-enforcer-plugin).
My bet is that there is another version of the jar somewhere in CDP, and occasionally it is loaded before the version that you ship with your project, causing the error.
So, when your container starts, try logging from which location the conflicting class is loaded. This question might help you: Determine which JAR file a class is from

How can I have separate debug and release versions of gradle project that pull in different versions of the same class?

I have two versions of the same Java class (same name / methods). Since it's Java, both .java files have the same name. I want to configure gradle in such a way that I can build a "debug" version of my application that pulls in one of these files, and a "production" version of my application that pulls in the other one. How would I go about doing this?
This class has only static methods. I don't ever want to make an instance of this class. I additionally don't want to add the overhead of an if statement in each of the methods on this class to check which version I'm in.
Following #JFabianMeier's answer you could use 4 projects:
with the production version class
with the debug version class
with code that uses either of the two, parameterized according to Migrating Maven profiles ... → Example 6. Mimicking the behavior of Maven profiles in Gradle. (I'm also a Maven guy and therefore can't tell you exactly how to do it in Gradle.)
a multi-project with 1./2./3. as sub[-]projects for building all of them in one go, maybe parameterized to build just 1.+ 3. or 2.+ 3.
Have you tried creating production and debug source directories/sets? According to the docs you can use multiple directories when specifying source directories in your source set. Try dropping the different versions of your class in their respective production/debug source directories.
I haven't tested myself (not sure about the syntax), but this is based on the Gradle's Java compilation documentation.
sourceSets {
// Could also name this production
main {
java {
srcDirs ['src/main/java', 'src/prod/java']
}
}
debug {
java {
srcDirs ['src/main/java', 'src/debug/java']
}
}
}
You could do the following:
Put the class into a separate project (so generate a separate jar from it)
Then you can have two different jars, for production and debugging
Then you can pull either one or the other jar in gradle depending on a parameter.
Alternatively, you could look into template engines like Velocity which allow you to generate source code during the build depending on variables and then compile it.
Android has a neat feature called Product Flavors. It lets you swap classes at compile time effortlessly and keep your project clean.
This post is very good to get a taste of it: https://android-developers.googleblog.com/2015/12/leveraging-product-flavors-in-android.html
And here is the full documentation: https://developer.android.com/studio/build/build-variants#product-flavors

maven-shade-plugin and singletons

I think I already know the answer, but would appreciate if someone can confirm it for me.
A) We are including a library in a build using maven-shade-plugin;
B) This library implements singleton(s);
C) Our resulting jar (uber-jar) is, in turn, used in a build of a megaproject;
D) Other jars in a megaproject also using the same library (A);
Would the library (A) be not acting as a singleton across the entire megaproject?
Would we end up with a separate set of static variables for each shaded copy of an artifact?
Would using maven-assembly-plugin instead of maven-shade plugin help?
You described a scenario in which the same class could end up in the classpath more than once.
In such an application the class loader will pick up the first one found when looking for that class. Once it has found it, this class is loaded and initialized by this class loader, and he will not look it up again. Usually that does not lead to problems, and you will still have the singleton as you want: one instance only.
If - for some reason - you have multiple class loaders at hand, they each could load this class from another location. So you could end with several singleton instances.
The shade plugin seems not to be the best tool for that. I would suggest to use it only for standalone applications that you package in a single shaded JAR - the end product.
We always use the assembly plugin because it gives a more fine-grained control over the packaged assembly. But you should not use shaded JARs as dependencies, instead simply use the core libraries. Even if you have the same dependency in various dependency paths in your project, the assembly plugin will package it only once.

Order of loading jars

Assuming there are two jars of different library versions on a classpath, e.g.
java -cp A-2.1.jar:A-2.2.jar ...
The package and class names in the first and second jars are the same, but class implementation is different. Is it specified whether root jvm classloader will try to find a class in A-2.1 before A-2.2?
The problem is that AWS EMR adds hadoop jars to a classpath and some of its dependencies are of older versions. However, our application needs to use new versions of the same libraries, so will prepending the classpath with newer versions of libraries be enough or is shading a recommended practice in this case? http://docs.aws.amazon.com/ElasticMapReduce/latest/DeveloperGuide/emr-hadoop-config_hadoop-user-env.sh.html
From the Setting the Class Path documentation:
The order in which you specify multiple class path entries is
important. The Java interpreter will look for classes in the
directories in the order they appear in the class path variable.
That said, overriding the dependency JARs of another library will always be risky since the library provider might not have tested that combination, so you'll either need to ask them for reassurance, do your own testing, or shade/repackage the classes as you suggested.

Proguard obfuscation of multi jar project

I have successfully configured Proguard with Maven to obfuscate a jar, and its dependant jar. I have managed to get both obfuscations to use the same mapping file, so that one jar can call the methods of the other. The problem I am facing, is that Proguard is not keeping unique names across the obfuscated jars; both obfuscated jars contain a class called
f.b.class
As there are two classes called f.b.class (one in each jar), priority is being given to the class inside the calling jar, which is causing problems.
Has anybody experienced this before and are you aware of a solution for this. Currently I am using the
-keeppackagenames
switch to ensure that the package hierarchy remain different so that any duplicated class names do not conflict. Ideally I would like to remove all package names
The switch
-useuniqueclassmembernames
has also been applied but it clearly only applies this to the jar currently being obfuscated. It does don't look and previously obfuscated jars to ensure uniqueness across jars.
Thanks
To resolve this I ended up using the -keeppackagenames option. It is not a solution, but a work around.

Categories