261

I'm packaging a Java library as a JAR, and it's throwing many java.lang.IncompatibleClassChangeErrors when I try to invoke methods from it. These errors seem to appear at random. What kinds of problems could be causing this error?

sepp2k
  • 363,768
  • 54
  • 674
  • 675
Zombies
  • 25,039
  • 43
  • 140
  • 225
  • In an Eclipse project where I was testing Apache FOP 1.0 and Barcode4J, the additional libraries that came with Barcode4J were apparently overriding the ones that came with FOP (some had higher version numbers). It's a case for being very careful what you put in your build path / classpath. – Wivani Jun 15 '11 at 08:06

21 Answers21

197

This means that you have made some incompatible binary changes to the library without recompiling the client code. Java Language Specification §13 details all such changes, most prominently, changing non-static non-private fields/methods to be static or vice versa.

Recompile the client code against the new library, and you should be good to go.

UPDATE: If you publish a public library, you should avoid making incompatible binary changes as much as possible to preserve what's known as "binary backward compatibility". Updating dependency jars alone ideally shouldn't break the application or the build. If you do have to break binary backward compatibility, it's recommended to increase the major version number (e.g. from 1.x.y to 2.0.0) before releasing the change.

Pr0methean
  • 303
  • 4
  • 14
notnoop
  • 58,763
  • 21
  • 123
  • 144
  • 3
    For some reason the developers here are having a problem where recompiling the client code doesn't fix the issue exactly. For some reason if they edit the file where it occurs and recompile the error no longer occurs there, but more will randomly pop-up elsewhere in the project, where the library is referenced. I am curious what could possibly be casuing this. – Zombies Dec 30 '09 at 15:42
  • 6
    Have you tried to do a clean build (delete all the `*.class` files) and recompiling? Editing files have similar effect. – notnoop Dec 30 '09 at 15:52
  • No dynamically generated code.... unless you would consider a JSP to be such. We did try deleting the class files and this didn't seem to help. The wierd thing is that it just doesn't seem to happen for me but it happens for other developer. – Zombies Dec 30 '09 at 16:54
  • 2
    Can you ensure that when you do a clean build you are compiling against the same jars you run with? – notnoop Dec 30 '09 at 19:18
  • Is there something concerned with 32bit or 64bit platform , I found a error only perform in 64bit platform. – cowboi-peng Sep 26 '17 at 03:21
110

Your newly packaged library is not backward binary compatible (BC) with old version. For this reason some of the library clients that are not recompiled may throw the exception.

This is a complete list of changes in Java library API that may cause clients built with an old version of the library to throw java.lang.IncompatibleClassChangeError if they run on a new one (i.e. breaking BC):

  1. Non-final field become static,
  2. Non-constant field become non-static,
  3. Class become interface,
  4. Interface become class,
  5. if you add a new field to class/interface (or add new super-class/super-interface) then a static field from a super-interface of a client class C may hide an added field (with the same name) inherited from the super-class of C (very rare case).

Note: There are many other exceptions caused by other incompatible changes: NoSuchFieldError, NoSuchMethodError, IllegalAccessError, InstantiationError, VerifyError, NoClassDefFoundError and AbstractMethodError.

The better paper about BC is "Evolving Java-based APIs 2: Achieving API Binary Compatibility" written by Jim des Rivières.

There are also some automatic tools to detect such changes:

Usage of japi-compliance-checker for your library:

japi-compliance-checker OLD.jar NEW.jar

Usage of clirr tool:

java -jar clirr-core-0.6-uber.jar -o OLD.jar -n NEW.jar

Good luck!

linuxbuild
  • 15,843
  • 6
  • 60
  • 87
73

While these answers are all correct, resolving the problem is often more difficult. It's generally the result of two mildly different versions of the same dependency on the classpath, and is almost always caused by either a different superclass than was originally compiled against being on the classpath or some import of the transitive closure being different, but generally at class instantiation and constructor invocation. (After successful class loading and ctor invocation, you'll get NoSuchMethodException or whatnot.)

If the behavior appears random, it's likely the result of a multithreaded program classloading different transitive dependencies based on what code got hit first.

To resolve these, try launching the VM with -verbose as an argument, then look at the classes that were being loaded when the exception occurs. You should see some surprising information. For instance, having multiple copies of the same dependency and versions you never expected or would have accepted if you knew they were being included.

Resolving duplicate jars with Maven is best done with a combination of the maven-dependency-plugin and maven-enforcer-plugin under Maven (or SBT's Dependency Graph Plugin, then adding those jars to a section of your top-level POM or as imported dependency elements in SBT (to remove those dependencies).

Good luck!

Brian Topping
  • 3,235
  • 28
  • 33
  • 7
    The verbose argument helped me pinpoint which jars were giving problems. Thanks – Virat Kadaru Jul 19 '13 at 23:11
  • 1
    As an alternative to the quite overwhelming -verbose output, you may be able to use the 'Depedency analyzer' in your IDE (IntelliJ IDEA in my case). Filter for warnings and read what dependencies are being warned about. – GerardV Aug 20 '23 at 12:16
8

I have also discovered that, when using JNI, invoking a Java method from C++, if you pass parameters to the invoked Java method in the wrong order, you will get this error when you attempt to use the parameters inside the called method (because they won't be the right type). I was initially taken aback that JNI does not do this checking for you as part of the class signature checking when you invoke the method, but I assume they don't do this kind of checking because you may be passing polymorphic parameters and they have to assume you know what you are doing.

Example C++ JNI Code:

void invokeFooDoSomething() {
    jobject javaFred = FredFactory::getFred(); // Get a Fred jobject
    jobject javaFoo = FooFactory::getFoo(); // Get a Foo jobject
    jobject javaBar = FooFactory::getBar(); // Get a Bar jobject
    jmethodID methodID = getDoSomethingMethodId() // Get the JNI Method ID


    jniEnv->CallVoidMethod(javaFoo,
                           methodID,
                           javaFred, // Woops!  I switched the Fred and Bar parameters!
                           javaBar);

    // << Insert error handling code here to discover the JNI Exception >>
    //  ... This is where the IncompatibleClassChangeError will show up.
}

Example Java Code:

class Bar { ... }

class Fred {
    public int size() { ... }
} 

class Foo {
    public void doSomething(Fred aFred, Bar anotherObject) {
        if (name.size() > 0) { // Will throw a cryptic java.lang.IncompatibleClassChangeError
            // Do some stuff...
        }
    }
}
Ogre Psalm33
  • 21,366
  • 16
  • 74
  • 92
  • 1
    Thanks for the hint. I had the same problem when calling a Java method with the wrong instance (this) supplied. – sstn Sep 19 '14 at 14:18
5

I had the same issue, and later I figured out that I am running the application on Java version 1.4 while the application is compiled on version 6.

Actually, the reason was of having a duplicate library, one is located within the classpath and the other one is included inside a jar file that is located within the classpath.

Eng.Fouad
  • 115,165
  • 71
  • 313
  • 417
  • How did you get to the bottom of this? Im sure I have a similar problem but dont have a clue how to trace which dependency library is being duplicated. – beterthanlife May 08 '13 at 10:21
  • @beterthanlife write a script that searches inside all the jar files looking for duplicated classes (search by their full-qualified name, i.e with package name) :) – Eng.Fouad May 08 '13 at 10:30
  • ok, using NetBeans and maven-shade I think I can see all of the classes which are being duplicated and the jar files they reside in. Many of these jar files I dont have directly referenced in my pom.xml, so I assume they must be being included by my dependencies. Is there an easy way to find out which dependency is including them, without going through each of the dependencies' pom file? (please pardon my noobishness, Im a java virgin!) – beterthanlife May 08 '13 at 10:41
  • @beterthanlife Better ask a new question regarding that :) – Eng.Fouad May 08 '13 at 10:42
  • I found a duplicate jar by looking at the gradle dependencies graph (./gradlew ::dependencies). So I found the culprit that drew the old version of the lib into the project. – nyx Apr 22 '15 at 14:42
2

In my case, I ran into this error this way. pom.xml of my project defined two dependencies A and B. And both A and B defined dependency on same artifact (call it C) but different versions of it (C.1 and C.2). When this happens, for each class in C maven can only select one version of the class from the two versions (while building an uber-jar). It will select the "nearest" version based on its dependency mediation rules and will output a warning "We have a duplicate class..." If a method/class signature changes between the versions, it can cause a java.lang.IncompatibleClassChangeError exception if the incorrect version is used at runtime.

Advanced: If A must use v1 of C and B must use v2 of C, then we must relocate C in A and B's poms to avoid class conflict (we have a duplicate class warning) when building the final project that depends on both A and B.

Community
  • 1
  • 1
morpheus
  • 18,676
  • 24
  • 96
  • 159
2

In my case the error appeared when I added the com.nimbusds library in my application deployed on Websphere 8.5.
The below exception occurred:

Caused by: java.lang.IncompatibleClassChangeError: org.objectweb.asm.AnnotationVisitor

The solution was to exclude the asm jar from the library:

<dependency>
    <groupId>com.nimbusds</groupId>
    <artifactId>nimbus-jose-jwt</artifactId>
    <version>5.1</version>
    <exclusions>
        <exclusion>
            <artifactId>asm</artifactId>
            <groupId>org.ow2.asm</groupId>
        </exclusion>
    </exclusions>
</dependency>
IDarkCoder
  • 709
  • 7
  • 18
amine
  • 29
  • 5
1

All of the above - for whatever reason I was doing some big refactor and starting to get this. I renamed the package my interface was in and that cleared it. Hope that helps.

bsautner
  • 4,479
  • 1
  • 36
  • 50
1

I have a web application that deploys perfectly fine on my local machine's tomcat(8.0.20). However, when I put it into the qa environment (tomcat - 8.0.20), it kept on giving me the IncompatibleClassChangeError and it was complaining that I was extending on an interface. This interface was changed to an abstract class. And I compiled the parent and child classes and still I kept on getting the same issue. Finally, I wanted to debug, so, I changed the version on the parent to x.0.1-SNAPSHOT and then compiled everything and now it is working. If someone is still hitting the problem after following the answers given here, please make sure the versions in your pom.xml are also correct. Change the versions to see if that works. If so, then fix the version problem.

Jobin Thomas
  • 111
  • 1
  • 8
1

My answer, I believe, will be Intellij specific.

I had rebuilt clean, even going as far as to manually delete the "out" and "target" dirs. Intellij has a "invalidate caches and restart", which sometimes clears odd errors. This time it didn't work. The dependency versions all looked correct in the project settings->modules menu.

The final answer was to manually delete my problem dependency from my local maven repo. An old version of bouncycastle was the culprit(I knew I had just changed versions and that would be the problem) and although the old version showed up no where in what was being built, it solved my problem. I was using intellij version 14 and then upgraded to 15 during this process.

David Nickerson
  • 144
  • 1
  • 5
1

An additional cause of this issue, is if you have Instant Run enabled for Android Studio.

The fix

If you find you start getting this error, turn off Instant Run.

  1. Android Studio main settings
  2. Build, Execution, Deployment
  3. Instant Run
  4. Untick "Enable instant run..."

Why

Instant Run modifies a large number of things during development, to make it quicker to provide updates to your running App. Hence instant run. When it works, it is really useful. However, when an issue such as this strikes, the best thing to do is to turn off Instant Run until the next version of Android Studio releases.

Knossos
  • 15,802
  • 10
  • 54
  • 91
1

Another situation where this error can appear is with Emma Code Coverage.

This happens when assigning an Object to an interface. I guess this has something to do with the Object being instrumented and not binary compatible anymore.

http://sourceforge.net/tracker/?func=detail&aid=3178921&group_id=177969&atid=883351

Fortunately this problem doesn't happen with Cobertura, so I've added cobertura-maven-plugin in my reporting plugins of my pom.xml

stivlo
  • 83,644
  • 31
  • 142
  • 199
1

I've faced this issue while undeploying and redeploying a war with glassfish. My class structure was like this,

public interface A{
}

public class AImpl implements A{
}

and it was changed to

public abstract class A{
}

public class AImpl extends A{
}

After stopping and restarting the domain, it worked out fine. I was using glassfish 3.1.43

Abbas Gadhia
  • 14,532
  • 10
  • 61
  • 73
0

Please check if your code doesnt consist of two module projects that have the same classes names and packages definition. For example this could happen if someone uses copy-paste to create new implementation of interface based on previous implementation.

denu
  • 2,170
  • 2
  • 23
  • 28
0

If this is a record of possible occurences of this error then:

I just got this error on WAS (8.5.0.1), during the CXF (2.6.0) loading of the spring (3.1.1_release) configuration where a BeanInstantiationException rolled up a CXF ExtensionException, rolling up a IncompatibleClassChangeError. The following snippet shows the gist of the stack trace:

Caused by: org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [org.apache.cxf.bus.spring.SpringBus]: Constructor threw exception; nested exception is org.apache.cxf.bus.extension.ExtensionException
            at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:162)
            at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:76)
            at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:990)
            ... 116 more
Caused by: org.apache.cxf.bus.extension.ExtensionException
            at org.apache.cxf.bus.extension.Extension.tryClass(Extension.java:167)
            at org.apache.cxf.bus.extension.Extension.getClassObject(Extension.java:179)
            at org.apache.cxf.bus.extension.ExtensionManagerImpl.activateAllByType(ExtensionManagerImpl.java:138)
            at org.apache.cxf.bus.extension.ExtensionManagerBus.<init>(ExtensionManagerBus.java:131)
            [etc...]
            at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:147)
            ... 118 more

Caused by: java.lang.IncompatibleClassChangeError: 
org.apache.neethi.AssertionBuilderFactory
            at java.lang.ClassLoader.defineClassImpl(Native Method)
            at java.lang.ClassLoader.defineClass(ClassLoader.java:284)
            [etc...]
            at com.ibm.ws.classloader.CompoundClassLoader.loadClass(CompoundClassLoader.java:586)
            at java.lang.ClassLoader.loadClass(ClassLoader.java:658)
            at org.apache.cxf.bus.extension.Extension.tryClass(Extension.java:163)
            ... 128 more

In this case, the solution was to change the classpath order of the module in my war file. That is, open up the war application in the WAS console under and select the client module(s). In the module configuration, set the class-loading to be "parent last".

This is found in the WAS console:

  • Applicatoins -> Application Types -> WebSphere Enterprise Applications
  • Click link representing your application (war)
  • Click "Manage Modules" under "Modules" section
  • Click link for the underlying module(s)
  • Change "Class loader order" to be "(parent last)".
wmorrison365
  • 5,995
  • 2
  • 27
  • 40
0

Documenting another scenario after burning way too much time.

Make sure you don't have a dependency jar that has a class with an EJB annotation on it.

We had a common jar file that had an @local annotation. That class was later moved out of that common project and into our main ejb jar project. Our ejb jar and our common jar are both bundled within an ear. The version of our common jar dependency was not updated. Thus 2 classes trying to be something with incompatible changes.

Snekse
  • 15,474
  • 10
  • 62
  • 77
0

For some reason the same exception is also thrown when using JNI and passing the jclass argument instead of the jobject when calling a Call*Method().

This is similar to the answer from Ogre Psalm33.

void example(JNIEnv *env, jobject inJavaList) {
    jclass class_List = env->FindClass("java/util/List");

    jmethodID method_size = env->GetMethodID(class_List, "size", "()I");
    long size = env->CallIntMethod(class_List, method_size); // should be passing 'inJavaList' instead of 'class_List'

    std::cout << "LIST SIZE " << size << std::endl;
}

I know it is a bit late to answer this question 5 years after being asked but this is one of the top hits when searching for java.lang.IncompatibleClassChangeError so I wanted to document this special case.

Eric Obermühlner
  • 1,076
  • 9
  • 9
0

Adding my 2 cents .If you are using scala and sbt and scala-logging as dependency ;then this can happen because scala-logging's earlier version had the name scala-logging-api.So;essentially the dependency resolutions do not happen because of different names leading to runtime errors while launching the scala application.

sourabh
  • 466
  • 4
  • 13
0

I got this error because I had an abstract base class which promised that it implements a certain interface, but I had forgotten to add the implementations of the interface methods, and then I created a non-abstract (concrete) bytecode-generated class which extended the abstract class, without providing implementations for those methods, either.

When I tried to create an instance the bytecode-generated class, the JVM complained with java.lang.IncompatibleClassChangeError.

Luckily, the exception has a "message" member which provides more detailed information as to what went wrong. In my case the message clearly said that the particular class was supposed to implement the particular interface, but it did not actually implement it.

Mike Nakis
  • 56,297
  • 11
  • 110
  • 142
0

If you came from android development. Then give a try of rebuild option might be fix for you.

Mohd Qasim
  • 896
  • 9
  • 20
0

In my case:

  • I have a project containing a few modules, including app, test, integrationTest
  • I created OneElementCache in app module.
  • Then, I created a file Cache in test module, the file contains some helpers for creating OneElementCache in tests.
  • Until now, everything works perfectly (both test and integrationTest passes).
  • After that, I created a file Cache in app module.
  • Got while running integrationTest:
Caused by: java.lang.IncompatibleClassChangeError: 
    class app.cache.CacheImpl can not implement app.cache.Cache, because it is not an interface (app.cache.Cache is in unnamed module of loader 'app')

The reason was a conflict in naming in different modules (app/test). Changing the filename in test did the job.

Cililing
  • 4,303
  • 1
  • 17
  • 35