是什么导致java.lang.IncompatibleClassChangeError?


218

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


在我测试Apache FOP 1.0和Barcode4J的Eclipse项目中,Barcode4J附带的其他库显然覆盖了FOP附带的库(有些库具有更高的版本号)。这是非常注意在构建路径/类路径中输入的内容的情况。
Wivani 2011年

Answers:


170

这意味着您对库进行了一些不兼容的二进制更改,而无需重新编译客户端代码。 Java语言规范§13详细介绍了所有这些更改,最显着的是将static非非私有字段/方法更改为static,反之亦然。

根据新库重新编译客户端代码,您应该一切顺利。

更新:如果发布公共库,则应尽可能避免进行不兼容的二进制更改,以保留所谓的“二进制向后兼容”。理想情况下,仅更新依赖项jar不会破坏应用程序或构建。如果必须中断二进制的向后兼容性,建议在发布更改之前增加主版本号(例如从1.xy到2.0.0)。


2
由于某些原因,这里的开发人员遇到了一个问题,即重新编译客户端代码不能完全解决问题。由于某种原因,如果他们在该文件所在的位置进行编辑并重新编译,该错误将不再在那里发生,但是会在项目中引用该库的其他地方随机弹出更多错误。我很好奇这可能是什么原因。
僵尸

5
您是否尝试过进行干净的构建(删除所有*.class文件)并重新编译?编辑文件具有类似的效果。
notnoop

没有动态生成的代码...。除非您认为JSP是这样的。我们确实尝试删除了类文件,但这似乎无济于事。奇怪的是,它似乎对我似乎没有发生,但对其他开发人员却发生。
僵尸

2
您可以确保在执行干净的构建时,是否针对与运行时相同的jar进行编译吗?
notnoop

是否与32bit或64bit平台有关,我发现一个错误仅在64bit平台上执行。
cowboi-peng

96

您新包装的库不向后二进制兼容与旧版本(BC)。因此,某些未重新编译的库客户端可能会引发异常。

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

  1. 非最终领域变为静态,
  2. 非常数场变为非静态,
  3. 类成为接口,
  4. 接口变成类,
  5. 如果您向类/接口添加新字段(或添加新的超类/超级接口),则客户端类C的超级接口中的静态字段可能会隐藏从继承的继承的同名字段(具有相同名称)。 C的超类(非常罕见的情况)。

注意:还有许多其他不兼容的更改引起的异常NoSuchFieldErrorNoSuchMethodErrorIllegalAccessErrorInstantiationErrorVerifyErrorNoClassDefFoundErrorAbstractMethodError

关于BC的更好的论文是Jim desRivières撰写的“正在发展的基于Java的API 2:实现API二进制兼容性”

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

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

祝好运!


59

尽管这些答案都是正确的,但解决问题通常更加困难。通常,这是对类路径的相同依赖关系的两个版本的略有不同的结果,并且几乎总是由与最初针对类路径的不同的超类与传递闭包的某些导入不同而导致的,但通常是在类上实例化和构造函数调用。(在成功加载类和调用ctor之后,您将得到NoSuchMethodException或得到什么。)

如果行为是随机的,则可能是多线程程序类根据首先被击中的代码加载了不同的传递依赖项的结果。

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

使用Maven解决重复的jar最好结合使用Maven下的maven-dependency-pluginmaven-enforcer-plugin(或SBT的Dependency Graph Plugin)来完成,然后将这些jar添加到顶级POM的一部分或作为导入的依赖SBT中的元素(删除那些依赖项)。

祝好运!


5
冗长的论点帮助我确定了哪个罐子出了问题。谢谢
Virat Kadaru

6

我还发现,当使用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...
        }
    }
}

1
感谢您的提示。使用提供的错误实例(此)调用Java方法时,我遇到了同样的问题。
sstn 2014年

5

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

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


您是如何找到根底的?我肯定我有一个类似的问题,但没有线索如何跟踪正在复制哪个依赖库。
13年

@beterthanlife编写了一个脚本,该脚本在所有jar文件中进行搜索以查找重复的类(按其完全限定的名称进行搜索,即使用包名称):)
Eng.Fouad 2013年

好的,使用NetBeans和maven-shade,我想我可以看到所有正在复制的类以及它们所驻留的jar文件。我在pom.xml中没有直接引用的许多jar文件,所以我认为它们必须被我的依赖项包括在内。有没有一种简单的方法可以找出哪个依赖项包括它们,而无需遍历每个依赖项的pom文件?(请原谅我的无礼,我是Java处女!)
beterthanlife

@beterthanlife最好再问一个新问题:)
Eng.Fouad 2013年

我通过查看gradle依赖关系图(./gradlew:<name>:dependencies)找到了一个重复的jar。因此,我找到了将旧版本的lib拉到项目中的罪魁祸首。
nyx


1

我在取消部署和重新部署与Glassfish的战争时遇到了这个问题。我的课堂结构是这样的,

public interface A{
}

public class AImpl implements A{
}

它被更改为

public abstract class A{
}

public class AImpl extends A{
}

停止并重新启动域后,它工作正常。我在用玻璃鱼3.1.43


1

我有一个Web应用程序,可以在本地计算机的tomcat(8.0.20)上完美部署。但是,当我将其放入qa环境(tomcat-8.0.20)时,它一直在给我IncompatibleClassChangeError,并且抱怨我在接口上扩展。该接口已更改为抽象类。我编译了父类和子类,但仍然不断遇到同样的问题。最后,我想调试,因此,我将父级的版本更改为x.0.1-SNAPSHOT,然后编译了所有内容,现在它可以工作了。如果遵循此处给出的答案后仍然有人遇到问题,请确保pom.xml中的版本也正确。更改版本以查看是否可行。如果是这样,则解决版本问题。


1

我相信我的答案将取决于Intellij。

我已经重建干净,甚至可以手动删除“ out”和“ target”目录。Intellij具有“使缓存无效并重新启动”的功能,该功能有时会清除奇数错误。这次没有用。在项目设置->模块菜单中,所有依赖版本都看起来正确。

最后的答案是从本地Maven存储库中手动删除我的问题依赖项。罪魁祸首是旧版本的罪魁祸首(我知道我刚刚更改了版本,这就是问题所在),尽管旧版本在构建的内容中没有显示任何位置,但它解决了我的问题。我使用的是intellij版本14,然后在此过程中升级到15。


1

就我而言,我以这种方式遇到了这个错误。pom.xml我的项目中定义了两个依赖AB。而且两者AB在同一工件(称之为定义的依赖C),但不同版本的它(C.1C.2)。发生这种情况时,对于Cmaven中的每个类,只能从两个版本中选择一个版本(在构建uber-jar时)。它将根据其依赖关系调解规则选择“最近”版本,并输出警告“我们有重复的类...”。如果版本之间的方法/类签名发生更改,则java.lang.IncompatibleClassChangeError如果版本不正确,则会导致异常在运行时使用。

高级:如果A必须使用V1的CB必须使用V2 C,那么我们就必须重新定位 CAB构建既取决于最终的项目时的,以避免阶级冲突劲歌(我们有一个重复类警告)AB


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>

0

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


0

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

在弹簧(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控制台中可以找到:

  • 应用程序->应用程序类型-> WebSphere企业应用程序
  • 单击代表您的应用的链接(战争)
  • 点击“模块”部分下的“管理模块”
  • 单击基础模块的链接
  • 将“类加载器顺序”更改为“(最后父项)”。

0

刻录过多时间后记录另一个场景。

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

我们有一个带有@local注释的通用jar文件。该课程后来从该普通项目中移出,进入了我们的主要ejb jar项目。我们的ejb罐子和我们的普通罐子都捆绑在耳朵内。我们常见的jar依赖版本未更新。因此,两个类试图成为具有不兼容更改的东西。


0

以上所有-无论出于何种原因,我都在进行一些较大的重构并开始理解这一点。我重命名了接口所在的包,然后将其清除。希望能有所帮助。


0

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

这类似于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因此我想记录这个特殊情况。


0

再加上我的2美分。如果您使用scala和sbt以及scala-logging作为依赖项,则可能会发生这种情况,因为scala-logging的早期版本的名称为scala-logging-api。启动scala应用程序时导致运行时错误的名称。


0

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

解决方法

如果发现开始出现此错误,请关闭Instant Run

  1. Android Studio主要设置
  2. 构建,执行,部署
  3. 即时运行
  4. 取消选中“启用即时运行...”

为什么

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.