Wednesday, April 25, 2012

What causes java.lang.IncompatibleClassChangeError?


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



Source: Tips4all

6 comments:

  1. This means that you have made some incompatible binary changes to the library without recompiling the client code. Java Language Specification S13 details all of such changes, most prominantly, changing non-static non-private fields/methods as static or visa-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.

    ReplyDelete
  2. 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):


    Non-final field become static,
    Non-constant field become non-static,
    Class become interface,
    Interface become class,
    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 a lot of automatic tools to detect such changes:


    Java API Compliance Checker (home)
    Clirr
    Japitools
    SigTest
    Japi-checker


    Usage of Java API Compliance Checker for your library (*.jar):

    japi-compliance-checker -lib NAME -d1 OLD.jar -d2 NEW.jar


    Usage of Clirr tool:

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


    Good luck!

    ReplyDelete
  3. While these answers are all correct, resolving the problem is often more difficult. It's generally the result of two wildly different versions of a 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.

    Resolving duplicate jars with Maven is best done with a combination of the maven-dependency-plugin and maven-enforcer-plugin, then adding those jars to a section of your top-level POM.

    Good luck!

    ReplyDelete
  4. 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...
    }
    }
    }

    ReplyDelete
  5. 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

    ReplyDelete
  6. 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

    ReplyDelete