gson.JsonObject and class loading creating very odd situations - java

I have some maven projects with dependencies back to:
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.1</version>
</dependency>
resulting in download of a single gson-2.8.1.jar file. I retrieve a copy of this file using the "clean package" maven build so I can reference a single directory for shared libraries used by classes dynamically loaded by my web application.
Due to the war file build policies (using "clean install deploy" maven build) the war file is also getting a copy of the gson-2.8.1.jar file in its WEB-INF/lib directory. As far as I know, these are all from the same jar file.
However, when running the web application and using a call like:
JsonObject retVal = null;
try {
paramData.add(0, userID);
paramData.add(0, session);
retVal = (JsonObject) method.invoke(null,
paramData.toArray(new Object[0]));
} catch (Exception e) {
I get an odd exception like:
"Caused by: java.lang.ClassCastException: com.google.gson.JsonObject cannot be cast to com.google.gson.JsonObject"
I don't understand why the same JsonObject class loaded from the gson-2.8.1.jar file in the shared library directory is not compatible with the JsonObject class that may be being loaded from the gson-2.8.1.jar in the war file. They have the same size, etc. and are the same class... because they all originated from the same gson-2.8.1.jar file...
I've tried to isolate the classes so only the one jar file in the shared library directory is called (e.g., removing it from the war file). However, in some cases I'll create a class not found exception when attempting to dynamically load the class.
I realize my description is hard to follow. Is there some reason the JsonObject loaded from this jar would result in Java thinking it has two incompatible classes solely because they may have been loaded by different class loaders when what is loaded is the same object?

What you're observing is expected classloading behavior. If you load the same Foo class from two different sources, from the JVM perspective these are two different classes and as such cannot be casted to one another (i.e. Foo cannot be cast to Foo). Likewise, if you have the same class Foo loaded from two different classloaders, these also cannot be cast to one another.
To resolve this issue, simply eliminate one of the copies. Since you have gone through the effort of setting up a shared library (which is the preferred approach), simply remove the gson jar from your apps WEB-INF/lib dir by changing the scope of your maven dependency to `provided.
For example:
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.1</version>
<scope>provided</scope>
</dependency>
This tells maven to include this dependency at compile time, but not at runtime (because we know it will be provided by the shared library).

Related

Two jars having same package name, same class name and same method name

I am facing this problem in spring boot application where I am using Two jars. And in those jars there are two classes with the same package name and same class name and one method with the same name . Problem occurs when class loader loads the class from different jar and tries to call this method, If jar A is loaded first then method is called and everything works fine , But if jar B is called first then, NOSUCHMETHODERROR exception is thrown at runtime. Can you please help to fix this. Is there a way by which I can force the classloader to load a particular jar first, always in Spring boot app, Or probably any other fix for this.
NOTE: I am using JAVA 8 and I don't have access to the source code of these jar files.
If you are loading those jars as a dependency you could exclude it from Maven via
<exclusions>
<exclusion>
<groupId>org.XXXX</groupId>
<artifactId>jar1-api</artifactId>
</exclusion>
</exclusions>

Resolving to correct dependencies when loading classes with custom classloaders

So I have this generic backend server that loads shaded jars in memory and then loads it through a custom Classloader.
E.g.
MyClass class = c.newInstance();
It works fine until the shaded Jar dependencies conflicts with the server classes.
E.g.
Server contains (with Custom Classloader):
com.fasterxml.jackson.jackson-databind:2.6.0
While the shaded jar contains
com.fasterxml.jackson.jackson-databind:2.9.9
When the method in the class that requires the said library e.g. class.doSomeThing(); it throws an error Caused by java.lang.NoSuchFieldError: because the loaded jackson-databind is 2.6.0 instead of 2.9.9
The question here is when the class is loaded from the shaded jar is there a way to make sure that the shaded dependencies are the ones used?
The question here is when the class is loaded from the shaded jar is there a way to make sure that the shaded dependencies are the ones used?
If you are using the default Class loader then the order of resolution will work as the order of the classpath. Within your code you can use
System.out.println(System.getProperty("system.class.path").replaceAll(":", "\n"));
And inspect the classpath. Usually such runtime environment (for example apache spark) has such features to allow you to prepend the classpath. You can check with your runtime server environment for such feature.

HTTP Status 500 - java.lang.LinkageError

I am developing an MVC web application in java using spring framework and maven tool using .
I am getting the following error when i am running my application.
HTTP Status 500 - java.lang.LinkageError: loader constraint violation: when resolving method
"org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(Ljavax/servlet/ServletConfig;)Lorg/apache/tomcat/InstanceManager;" the class loader (instance of org/apache/jasper/servlet/JasperLoader) of the current class,
org/apache/jsp/redirect_jsp, and the class loader (instance of org/apache/catalina/loader/StandardClassLoader) for the method's defining class,
org/apache/jasper/runtime/InstanceManagerFactory, have different Class objects for the type org/apache/tomcat/InstanceManager used in the signature
You have server specific jar files in your WEB-INF/lib folder of your web application. For ex : jsp-api.jar, el-api.jar, servlet-api.jar etc. You need to remove exclude all these if you are using maven for dependency management.
And after removing it, if you are getting compilation error in your code, then add server runtime from Project properties.
Is there servlet-api.jar is part of war file ? Please remove it during deployment because every webserver has there own servlet-api implementation. So only use it in your code for compilation purpose.
Based on the error you posted, the classloader that is loading org/apache/jsp/redirect_jsp, and the class loader for
org/apache/jasper/runtime/InstanceManagerFactory, have different Class objects for the type org/apache/tomcat/InstanceManager which means you are referencing a jar that contains org.apache.tomcat.InstanceManager in two different class loaders and those loaders have a delegation relationship. Class objects are unique based on their fully qualified class name and their loader.
Here is a pretty good article to read about this type of error.
You need to find out how you are loading the class twice and modify your setup so that you only load it once.
As mentioned in previous posts, it should be due to conflicting libraries in your project (jsp-api, servlet-api, el-api etc) which you need to exclude.
If you are using spring-boot, you need to exclude tomcat libs when you are generating a war to be deployed in tomcat.
For ex.
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
</exclusion>

Set the JAXB context factory initialization class to be used

I have updated our projects (Java EE based running on Websphere 8.5) to use a new release of a company internal framework (and Ejb 3.x deployment descriptors rather than the 2.x ones). Since then my integration Tests fail with the following exception:
[java.lang.ClassNotFoundException: com.ibm.xml.xlxp2.jaxb.JAXBContextFactory]
I can build the application with the previous framework release and everything works fine.
While debugging i noticed that within the ContextFinder (javax.xml.bind) there are two different behaviours:
Previous Version (Everything works just fine): None of the different places brings up a factory class so the default factory class gets loaded which is com.sun.xml.internal.bind.v2.ContextFactory (defined as String constant within the class).
Upgraded Version (ClassNotFound): There is a resource "META-INF/services/javax.xml.bind.JAXBContext" beeing loaded successfully and the first line read makes the ContextFinder attempt to load "com.ibm.xml.xlxp2.jaxb.JAXBContextFactory" which causes the error.
I now have two questions:
What sort is that resource? Because inside our EAR there is two WARs and none of those two contains a folder services in its
META-INF directory.
Where could that value be from otherwise? Because a filediff showed me no new or changed properties files.
No need to say i am going to read all about the JAXB configuration possibilities but if you have first insights on what could have gone wrong or help me out with that resource (is it a real file i have to look for?) id appreciate a lot. Many Thanks!
EDIT (according to comments Input/Questions):
Out of curiosity, does your framework include JAXB JARs? Did the old version of your framework include jaxb.properties?
Indeed (i am a bit surprised) the framework has a customized eclipselink-2.4.1-.jar inside the EAR that includes both a JAXB implementation and a jaxb.properties file that shows the following entry in both versions (the one that finds the factory as well as in the one that throws the exception):
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
I think this is has nothing to do with the current issue since the jar stayed exactly the same in both EARs (the one that runs/ the one with the expection)
It's also not clear to me why the old version of the framework was ever selecting the com.sun implementation
There is a class javax.xml.bind.ContextFinder which is responsible for initializing the JAXBContextFactory. This class searches various placess for the existance of a jaxb.properties file or a "javax.xml.bind.JAXBContext" resource. If ALL of those places dont show up which Context Factory to use there is a deault factory loaded which is hardcoded in the class itself:
private static final String PLATFORM_DEFAULT_FACTORY_CLASS = "com.sun.xml.internal.bind.v2.ContextFactory";
Now back to my problem:
Building with the previous version of the framework (and EJB 2.x deployment descriptors) everything works fine). While debugging i can see that there is no configuration found and thatfore above mentioned default factory is loaded.
Building with the new version of the framework (and EJB 3.x deployment descriptors so i can deploy) ONLY A TESTCASE fails but the rest of the functionality works (like i can send requests to our webservice and they dont trigger any errors). While debugging i can see that there is a configuration found. This resource is named "META-INF/services/javax.xml.bind.JAXBContext". Here are the most important lines of how this resource leads to the attempt to load 'com.ibm.xml.xlxp2.jaxb.JAXBContextFactory' which then throws the ClassNotFoundException. This is simplified source of the mentioned javax.xml.bind.ContextFinder class:
URL resourceURL = ClassLoader.getSystemResource("META-INF/services/javax.xml.bind.JAXBContext");
BufferedReader r = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "UTF-8"));
String factoryClassName = r.readLine().trim();
The field factoryClassName now has the value 'com.ibm.xml.xlxp2.jaxb.JAXBContextFactory'
Because this has become a super lager question i will also add a bounty :)
I will work the entire day on this and let you know if there is any news.
Update/ Solution
This question has been solved. The original problem has occured because misconfiguration of complexly build multi model maven projects which one dependency used a updated version of a customized eclipse link jar that contained a definition for a JAXBFactory not available in the component where the error occured. Setting the JAXB context factory in most cases is configured with a jaxb.propertie file or JAXBContext file that contains the same definition. Detailed loading process of the appropriate JAXBContextFactory happens in javax.xml.bind.ContextFinder.
The error has not yet been solved (during the fact over 4 major EE/SE Applications lead to the error) and there is no general answer but that defined JAXBContextFactorys must exist in your classpath (wow what a wonder...) so you either have a that ClassNotFound Error because youre missing resources (well thats the acctual cause) or because you have a wrong JAXBContextFactory defined in any of the above mentioned propertie files which contain a definition according to the below answer.
Very many thanks for your great comments and support, i realy appreciate!
You can include a jaxb.properties file in the same package as your domain model to specify the JAXB (JSR-222) implementation you wish to use. For example it would look like the following to specify EclipseLink MOXy as your JAXB provider.
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
For More Information
http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html
Another quick and dirty solution (a workaround, really) that worked for me is to explicitly include a JAXB implementation to the maven build. For example
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.2.7</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.2.7</version>
</dependency>
Note that this adds a somehow unnecessary dependency to your build, as JAXB obviously already is part of each JRE >= version 6.
Most likely this will only work when the WAS classloader is set to parent last.

Classloaders and sharing .jar files with Apache Tomcat

If I have classes that need to be shared between my webapp and Tomcat (e.g. a custom realm and principal), where should the .jar file containing those classes go?
Currently I'm putting the .jar in ${CATALINA_HOME}/lib. This result is a ClassCastException when assigning references from classes of the same type. Here's an example:
MyCustomPrincipal principal = (MyCustomPrincipal)FacesContext.getCurrentInstance().getExternalContext().getUserPrincipal();
The method above throws a ClassCastException. The method returns an actual MyCustomPrincipal type (since that's what my custom realm gave Tomcat when it performed authentication) that, apparently, was created by a different classloader. How do I fix this so both Tomcat and my webapp can use MyCustomPrincipal?
http://tomcat.apache.org/tomcat-6.0-doc/class-loader-howto.html
Any help is appreciated.
Andrew
It looks like you have 2 copies loaded, once in tomcat and once in your WEB-INF/lib jars or other classpath of your deployed application.
The reason you get classpath exception lies in the way a WAR looks for classes. Contrary to the normal Java rules, a war first looks inside the war for a class and only then passes the request to teh parent classloader.
A class's identity is dependent of the classloader and the same class loaded in 1 classloader will generate a classcast exception when it is casted in the other classloader.
The solution is to make sure that the war does not contain the classes which should be provided by the container. If you use maven you can mark these dependencies as 'provided', if you use ant, you have to split your classpath list in 2 and compile against both, but use only the ones you need for constructing the war.

Categories