什么导致java.lang.IncompatibleClassChangeError?

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

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

根据新的库重新编译客户端代码,你应该很好去。

更新:如果你发布一个公共库,你应该尽可能地避免不相容的二进制修改,以保持所谓的“二进制向后兼容性”。 理想情况下更新依赖关系jar不应该破坏应用程序或构build。

新打包的库不是与旧版本的后向二进制兼容 (BC)。 由于这个原因,一些没有重新编译的库客户端可能会抛出exception。

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

  1. 非决赛场地变得静止,
  2. 非常量字段变成非静态字段,
  3. 类成为接口,
  4. 接口成为上课,
  5. 如果向类/接口添加新的字段(或者添加新的超级类/超级接口),则来自客户端类C的超级接口的静态字段可以隐藏从该类inheritance的添加字段(具有相同的名称)超级C(非常罕见的情况)。

注意 :其他不兼容的更改还有很多其他exceptionNoSuchFieldErrorNoSuchMethodErrorIllegalAccessErrorInstantiationErrorVerifyErrorNoClassDefFoundErrorAbstractMethodError

关于BC的更好的文章是Jim desRivières写的“演进基于Java的API 2:实现API二进制兼容性” 。

还有一些自动工具来检测这种变化:

  • JAPI遵守检查器
  • clirr
  • japitools
  • 采用SigTest
  • JAPI检查器

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 

祝你好运!

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

如果行为是随机出现的,那么很可能是multithreading程序根据哪个代码首先被命中加载不同的传递依赖的结果。

要解决这些问题,请尝试使用-verbose作为参数启动VM,然后查看发生exception时正在加载的类。 你应该看到一些令人惊讶的信息。 例如,如果你知道它们被包含在内,那么就有多个你从未想过或者会接受的相同的依赖和版本的副本。

使用Maven解决重复的jar问题最好使用Maven(或SBT的依赖图插件)中的maven-dependency-plugin和maven-enforcer-plugin组合 ,然后将这些jar添加到顶级POM的一部分或导入的依赖项SBT中的元素(去除这些依赖)。

祝你好运!

我还发现,当使用JNI从C ++调用Java方法时,如果以错误的顺序将parameter passing给被调用的Java方法,当您尝试在被调用的方法内使用参数时会出现此错误(因为它们将不是正确的types)。 最初让我吃了一惊,即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... } } } 

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

实际上,原因是有一个重复的库,一个位于类path中,另一个位于类path中的jar文件中。

Emma Code Coverage(Emma代码覆盖率)是另一种可能出现此错误的情况。

将对象分配给接口时会发生这种情况。 我想这跟被测物体有关,而不是二进制兼容。

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

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

我面临这个问题,同时取消部署和重新部署与玻璃鱼的战争。 我的class级结构是这样的,

 public interface A{ } public class AImpl implements A{ } 

它被改为

 public abstract class A{ } public class AImpl extends A{ } 

停止并重新启动域后,它工作得很好。 我正在使用glassfish 3.1.43

我有一个web应用程序,可以在我的本地机器的tomcat(8.0.20)上完全部署。 但是,当我把它放到qa环境(tomcat – 8.0.20)时,它一直在给我IncompatibleClassChangeError,并且抱怨我正在扩展一个接口。 这个接口被改为一个抽象类。 我编写了父母和孩子class级,但仍然一直在争论同一个问题。 最后,我想debugging,所以,我将父版本更改为x.0.1-SNAPSHOT,然后编译所有内容,现在它正在工作。 如果在遵循此处给出的答案之后仍有人遇到问题,请确保您的pom.xml中的版本也是正确的。 更改版本,看看是否有效。 如果是这样,那么修复版本问题。

就我而言,我遇到了这样的错误。 我的项目的pom.xml定义了两个依赖关系AB AB定义了对同一个工件(称为C )的依赖关系,但是它的不同版本( C.1C.2 )。 发生这种情况时, C maven中的每个类只能从两个版本中select一个版本的类(在创build超级jar时 )。 它将根据它的依赖调解规则select“最近的”版本,并将输出一个警告“我们有一个重复的类…”如果一个方法/类签名在版本之间改变,它可能会导致一个java.lang.IncompatibleClassChangeErrorexception如果在运行时使用不正确的版本。

高级:如果A必须使用C v1, B必须使用C v2,那么我们必须在AB的poms中重新定位 C ,以避免在build立最终项目时产生类冲突(我们有一个重复的类警告) AB

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

如果这是该错误可能发生的logging,则:

在Spring(3.1.1_release)configuration的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 

在这种情况下,解决scheme是在我的war文件中更改模块的类path顺序。 也就是说,打开WAS控制台下的war应用程序,并select客户端模块。 在模块configuration中,将类加载设置为“父最后”。

这在WAS控制台中find:

  • 应用程序 – >应用程序types – > WebSphere企业应用程序
  • 点击表示你的应用程序的链接
  • 点击“模块”一节下的“pipe理模块”
  • 点击底层模块的链接
  • 将“类装入程序”更改为“(最后一个)”。

烧完后logging另外一个场景太多的时间。

确保你没有一个具有EJB注解的类的依赖jar。

我们有一个通用的jar文件,它有一个@local注释。 那个class后来被搬出了这个共同的项目,进入我们的主要ejb jar项目。 我们的ejbjar子和我们常见的jar子都捆在耳朵里。 我们常见的jar依赖的版本没有更新。 因此,2个类试图成为不兼容的变化的东西。

所有上述 – 不pipe出于什么原因,我正在做一些大的重构,并开始得到这个。 我重命名了我的接口所在的包,并清除了它。 希望有所帮助。

我相信,我的答案将是Intellij特有的。

我已经重build干净,甚至手动删除“出”和“目标”目录。 Intellij有一个“无效的caching并重新启动”,有时清除奇怪的错误。 这一次没有用。 依赖版本在项目设置 – >模块菜单中看起来都是正确的。

最终的答案是手动删除我的本地maven回购问题依赖。 一个老版本的bouncycastle是罪魁祸首(我知道我只是改变了版本,这将是问题),虽然旧版本没有出现在什么地方正在build造,它解决了我的问题。 我正在使用intellij版本14,然后在这个过程中升级到15。

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

这与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年之后回答这个问题有点晚,但是这是searchjava.lang.IncompatibleClassChangeError时的顶级命中之一,所以我想logging这个特殊情况。

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

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

修复

如果您发现您开始出现此错误,请closuresInstant Run

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

为什么

Instant Run在开发过程中会修改大量的事物,以便更快地为正在运行的应用程序提供更新。 因此即时运行。 当它起作用时,这真的很有用。 但是,如果出现这样的问题,最好的办法就是closuresInstant Run直到下一个版本的Android Studio发布。