是什么导致java.lang.IncompatibleClassChangeError?

时间:2009-12-30 14:24:23

标签: java jar runtime-error binary-compatibility

我正在将Java库打包为JAR,当我尝试从中调用方法时,它会抛出许多java.lang.IncompatibleClassChangeError。这些错误似乎是随机出现的。哪种问题可能导致此错误?

18 个答案:

答案 0 :(得分:158)

这意味着您已对库进行了一些不兼容的二进制更改,而无需重新编译客户端代码。 Java Language Specification §13详细说明了所有此类更改,最突出的是,将非static非私有字段/方法更改为static,反之亦然。

针对新库重新编译客户端代码,你应该好好去。

更新:如果您发布了公共库,则应尽可能避免进行不兼容的二进制更改,以保留所谓的“二进制向后兼容性”。理想情况下,单独更新依赖关系jar不应该破坏应用程序或构建。如果你必须打破二进制向后兼容性,那么在发布更改之前,recommended需要增加主版本号(例如从1.x.y增加到2.0.0)。

答案 1 :(得分:95)

新打包的库与旧版本不具有向后二进制兼容(BC)。因此,一些未重新编译的库客户端可能会抛出异常。

这是Java库API中的完整更改列表,可能导致使用旧版本库构建的客户端抛出java.lang。 IncompatibleClassChangeError 如果它们运行在一个新的(即破坏BC):

  1. 非最终字段变为静态,
  2. 非常数字段变为非静态字段,
  3. 班级成为界面,
  4. 界面成为课程,
  5. 如果向类/接口添加新字段(或添加新的超类/超级接口),则来自客户端类C的超级接口的静态字段可能隐藏添加的字段(具有相同的名称)继承自超级C(非常罕见的情况)。
  6. 注意:其他不兼容的更改导致了许多其他异常 NoSuchFieldError NoSuchMethodError IllegalAccessError InstantiationError VerifyError NoClassDefFoundError AbstractMethodError

    有关BC的更好的论文是"Evolving Java-based APIs 2: Achieving API Binary Compatibility"由JimdesRivières写的。

    还有一些自动工具可以检测到此类更改:

    使用japi-compliance-checker为您的图书馆:

    japi-compliance-checker OLD.jar NEW.jar
    

    使用clirr工具:

    java -jar clirr-core-0.6-uber.jar -o OLD.jar -n NEW.jar
    
    祝你好运!

答案 2 :(得分:54)

虽然这些答案都是正确的,但解决问题往往更加困难。它通常是类路径上相同依赖关系的两个略有不同版本的结果,并且几乎总是由不同于最初编译的超类引起的类路径传递闭包的某些导入不同,但通常在类实例化和构造函数调用。 (成功加载类和ctor后,你会得到NoSuchMethodException或其他什么。)

如果行为是随机的,那么很可能是多线程程序根据首先命中的代码加载不同的传递依赖性的结果。

要解决这些问题,请尝试以-verbose作为参数启动VM,然后查看发生异常时正在加载的类。你应该看到一些令人惊讶的信息。例如,拥有相同依赖关系和版本的多个副本,如果您知道它们被包含在内,则您从未预料到或将会接受这些副本。

最好使用Maven(或SBT的maven-dependency-plugin下的maven-enforcer-pluginDependency Graph Plugin组合使用Maven解决重复的jar,然后将这些jar添加到顶层的一部分POM或SBT中导入的依赖元素(删除这些依赖项)。

祝你好运!

答案 3 :(得分:5)

我还发现,在使用JNI时,从C ++调用Java方法,如果以错误的顺序将参数传递给调用的Java方法,当您尝试使用被调用方法中的参数时,将会出现此错误(因为它们不是正确的类型)。当你调用方法时,我最初认为JNI不会检查你作为类签名检查的一部分,但我认为它们不会进行这种检查,因为你可能正在传递多态参数而且他们必须假设你知道自己在做什么。

示例C ++ JNI代码:

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

示例Java代码:

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

答案 4 :(得分:5)

我遇到了同样的问题,后来我发现我在Java版本1.4上运行应用程序,而应用程序是在版本6上编译的。

实际上,原因是有一个重复的库,一个位于类路径中,另一个包含在位于类路径中的jar文件中。

答案 5 :(得分:1)

在我的情况下,当我在com.nimbusds上部署的应用程序中添加Websphere 8.5库时出现了错误。
发生以下异常:

  

原因:java.lang.IncompatibleClassChangeError:org.objectweb.asm.AnnotationVisitor

解决方案是从库中排除asm jar:

<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>

答案 6 :(得分:1)

在我的情况下,我以这种方式遇到了这个错误。我的项目pom.xml定义了两个依赖项ABAB都定义了对相同工件的依赖关系(称之为C),但它的版本不同(C.1C.2)。发生这种情况时,对于C中的每个类,maven只能从两个版本中选择一个版本的类(构建uber-jar时)。它将根据其dependency mediation规则选择“最近”版本并输出警告"We have a duplicate class..."如果某个方法/类签名在版本之间发生更改,则可能会导致java.lang.IncompatibleClassChangeError异常在运行时使用了不正确的版本。

高级:如果A必须使用C的v1且B必须使用C的v2,那么我们必须relocate C在构建依赖于AB的最终项目时,AB的poms可以避免类冲突(我们有一个重复的类警告)。

答案 7 :(得分:1)

我相信,我的答案将是Intellij具体的。

我已经重建了干净,甚至可以手动删除&#34; out&#34;和&#34;目标&#34;迪尔斯。 Intellij有一个&#34;无效的缓存并重新启动&#34;,它有时会清除奇怪的错误。这次它没有用。在项目设置 - >模块菜单中,依赖版本看起来都是正确的。

最后的答案是从我当地的maven repo手动删除我的问题依赖项。旧版本的bouncycastle是罪魁祸首(我知道我刚刚更改了版本,这将是问题)虽然旧版本没有出现在正在构建的地方,但它解决了我的问题。我正在使用intellij版本14,然后在此过程中升级到15。

答案 8 :(得分:1)

我有一个Web应用程序,可以在我的本地机器上运行tomcat(8.0.20)。但是,当我把它放入qa环境(tomcat - 8.0.20)时,它继续给我IncompatibleClassChangeError,它抱怨我正在扩展接口。此接口已更改为抽象类。我编写了父类和子类,但我仍然继续遇到同样的问题。最后,我想调试,所以,我将父版本的版本更改为x.0.1-SNAPSHOT,然后编译所有内容,现在它正在运行。如果有人在按照此处给出的答案后仍然遇到问题,请确保pom.xml中的版本也正确无误。更改版本以查看是否有效。如果是这样,那么修复版本问题。

答案 9 :(得分:1)

我在使用glassfish取消部署和重新部署战争时遇到了这个问题。我的班级结构是这样的,

public interface A{
}

public class AImpl implements A{
}

并且已更改为

public abstract class A{
}

public class AImpl extends A{
}

停止并重新启动域后,它运行良好。 我使用的是glassfish 3.1.43

答案 10 :(得分:1)

出现此错误的另一种情况是使用Emma Code Coverage。

将Object分配给接口时会发生这种情况。我想这与被检测的对象有关,而不再与二进制兼容。

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

幸运的是,这个问题不会发生在Cobertura上,所以我在我的pom.xml的报告插件中添加了cobertura-maven-plugin

答案 11 :(得分:0)

在刻录太多时间之后记录另一个场景。

确保没有依赖jar,其上有一个带有EJB注释的类。

我们有一个具有@local注释的公共jar文件。该课程后来被移出该共同项目并进入我们的主要ejb jar项目。我们的ejb jar和我们常见的jar都捆绑在耳朵里。我们常见的jar依赖项的版本没有更新。因此,有两个类试图成为具有不兼容变化的东西。

答案 12 :(得分:0)

以上所有 - 无论出于何种原因我都在做一些大型重构并开始得到这个。我重命名了我的界面所在的包,并清除了它。希望有所帮助。

答案 13 :(得分:0)

如果这是此错误可能发生的记录,那么:

我在Spring(3.1.1_release)配置的CXF(2.6.0)加载期间在WAS(8.5.0.1)上遇到此错误,其中BeanInstantiationException卷起了一个CXF ExtensionException,卷起了IncompatibleClassChangeError。以下代码段显示了堆栈跟踪的要点:

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

在这种情况下,解决方案是更改war文件中模块的类路径顺序。也就是说,在WAS控制台下打开war应用程序并选择客户端模块。在模块配置中,将类加载设置为“父最后一个”。

这可以在WAS控制台中找到:

  • Applicatoins - &gt;申请类型 - &gt; WebSphere Enterprise Applications
  • 点击代表您的应用程序的链接(战争)
  • 点击“模块”部分
  • 下的“管理模块”
  • 单击基础模块的链接
  • 将“类加载器顺序”更改为“(父级最后)”。

答案 14 :(得分:0)

请检查您的代码是否包含两个具有相同类名和包定义的模块项目。例如,如果有人使用复制粘贴来创建基​​于先前实现的接口的新实现,则可能发生这种情况。

答案 15 :(得分:0)

由于某种原因,在调用Call*Method()时使用JNI并传递jclass参数而不是jobject时也会抛出相同的异常。

这与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;
}

我知道在被问到这个问题5年后回答这个问题有点晚了,但这是搜索java.lang.IncompatibleClassChangeError时的热门搜索之一,所以我想记录这个特例。

答案 16 :(得分:0)

加上我的2美分。如果你使用scala和sbt以及scala-logging作为依赖;那么这可能发生,因为scala-logging的早期版本的名称为scala-logging-api.So;本质上,依赖项解析不会发生因为在启动scala应用程序时导致运行时错误的名称不同。

答案 17 :(得分:0)

此问题的另一个原因是,如果您为Android Studio启用了Instant Run

修复

如果您发现收到此错误,请关闭Instant Run

  1. Android Studio主要设置
  2. 构建,执行,部署
  3. 即时运行
  4. Untick“启用即时运行......”
  5. 为什么

    Instant Run在开发过程中修改了大量内容,以便更快地为正在运行的应用提供更新。因此即时运行。当它工作时,它真的很有用。但是,当出现此类问题时,最好的办法是关闭Instant Run,直到Android Studio的下一个版本发布。