Что вызывает java.lang.IncompatibleClassChangeError?

218

Я упаковываю библиотеку Java как JAR, и она выдает много java.lang.IncompatibleClassChangeErrors, когда я пытаюсь вызвать из нее методы. Эти ошибки кажутся случайными. Какие проблемы могут быть причиной этой ошибки?

Zombies
источник
В проекте Eclipse, где я тестировал Apache FOP 1.0 и Barcode4J, дополнительные библиотеки, поставляемые с Barcode4J, явно переопределяли те, которые шли с FOP (некоторые имели более высокие номера версий). Это тот случай, когда нужно быть очень осторожным с тем, что вы указали в пути сборки / пути к классам.
Вивани

Ответы:

170

Это означает, что вы внесли некоторые несовместимые двоичные изменения в библиотеку, не перекомпилировав код клиента. Спецификация языка Java § 13 подробно описывает все такие изменения, в первую очередь, изменение не- staticне приватных полей / методов на staticили наоборот.

Перекомпилируйте клиентский код с новой библиотекой, и вы должны быть в порядке.

ОБНОВЛЕНИЕ: Если вы публикуете публичную библиотеку, вам следует избегать как можно большего количества несовместимых двоичных изменений, чтобы сохранить так называемую «двоичную обратную совместимость». Обновление банок зависимостей в идеале не должно нарушать работу приложения или сборки. Если вам необходимо нарушить двоичную обратную совместимость, рекомендуется увеличить номер основной версии (например, с 1.xy до 2.0.0) перед тем, как отменить изменение.

notnoop
источник
2
По некоторым причинам у разработчиков здесь есть проблема, где перекомпиляция клиентского кода не решает проблему точно. По какой-то причине, если они отредактируют файл, в котором он находится, и перекомпилируют, ошибка больше не будет появляться там, а в других местах проекта, где есть ссылка на библиотеку, будет отображаться случайным образом. Мне любопытно, что может быть причиной этого.
Зомби
5
Вы пытались сделать чистую сборку (удалить все *.classфайлы) и перекомпилировать? Редактирование файлов имеет аналогичный эффект.
notnoop
Нет динамически сгенерированного кода .... если только вы не считаете JSP таким. Мы попытались удалить файлы классов, но это не помогло. Самое странное, что для меня этого просто не происходит, а для других разработчиков.
Зомби
2
Можете ли вы гарантировать, что когда вы делаете чистую сборку, вы компилируете с теми же файлами jar, с которыми вы работаете?
notnoop
Есть ли что-то, связанное с 32-битной или 64-битной платформой, я обнаружил ошибку, выполняемую только на 64-битной платформе.
Ковбой-Пэн
96

Ваша недавно упакованная библиотека не имеет обратной совместимости с двоичными файлами (BC) со старой версией. По этой причине некоторые из библиотечных клиентов, которые не перекомпилированы, могут выдать исключение.

Это полный список изменений в API библиотеки Java, которые могут привести к тому, что клиенты, созданные со старой версией библиотеки, сгенерируют java.lang. IncompatibleClassChangeError, если они запускаются на новом (то есть, ломая BC):

  1. Не финальные поля становятся статичными,
  2. Непостоянное поле становится нестатичным,
  3. Класс становится интерфейсом,
  4. Интерфейс стал классом,
  5. если вы добавляете новое поле в класс / интерфейс (или добавляете новый супер-класс / супер-интерфейс), то статическое поле из супер-интерфейса клиентского класса C может скрыть добавленное поле (с тем же именем), унаследованное от суперкласс C (очень редкий случай).

Примечание . Существует множество других исключений, вызванных другими несовместимыми изменениями: NoSuchFieldError , NoSuchMethodError , IllegalAccessError , InstantiationError , VerifyError , NoClassDefFoundError и AbstractMethodError .

Лучшая статья о BC - «Развитие API на основе Java 2: достижение двоичной совместимости API», написанная Jim des Riviè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

Удачи!

linuxbuild
источник
59

Хотя все эти ответы верны, решить проблему часто сложнее. Обычно это результат двух разных версий одной и той же зависимости от пути к классам, и почти всегда он вызван тем, что суперкласс отличается от того, который изначально был скомпилирован из-за того, что он находится в пути к классам, или некоторым изменением импорта транзитивного замыкания, но обычно в классе создание экземпляра и вызов конструктора. (После успешной загрузки класса и вызова ctor вы получите NoSuchMethodExceptionили еще что-нибудь .)

Если поведение кажется случайным, это, вероятно, результат многопоточной программы, загружающей различные транзитивные зависимости в зависимости от того, какой код был достигнут первым.

Чтобы устранить их, попробуйте запустить ВМ -verboseв качестве аргумента, а затем посмотрите на классы, которые загружались при возникновении исключения. Вы должны увидеть некоторую удивительную информацию. Например, наличие нескольких копий одной и той же зависимости и версий, которые вы никогда не ожидали или приняли бы, если бы знали, что они включены.

Устранение дубликатов jar-файлов с помощью Maven лучше всего выполнить с помощью комбинации плагинов maven-dependency-plugin и maven-inspecer-plugin в Maven (или подключаемого модуля SBT Dependency Graph , с последующим добавлением этих jar-файлов в раздел POM верхнего уровня или в качестве импортированной зависимости). элементы в SBT (чтобы удалить эти зависимости).

Удачи!

Брайан Топпинг
источник
5
Многословный аргумент помог мне точно определить, какие банки создавали проблемы. Спасибо
Вират Кадару
6

Я также обнаружил, что при использовании JNI, при вызове метода Java из C ++, если вы передадите параметры вызванному методу 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...
        }
    }
}
Огрский псалом33
источник
1
Спасибо за подсказку. У меня была такая же проблема при вызове метода Java с неправильным экземпляром (это).
sstn
5

У меня была та же проблема, и позже я понял, что я запускаю приложение на Java версии 1.4, в то время как приложение скомпилировано на версии 6.

На самом деле причина была в том, что у нас была дублирующаяся библиотека: одна находится в пути к классам, а другая включена в файл JAR, расположенный в пути к классам.

Eng.Fouad
источник
Как вы дошли до сути этого? Я уверен, что у меня похожая проблема, но я не знаю, как отследить, какая библиотека зависимостей дублируется.
beterthanlife
@beterthanlife напишите скрипт, который ищет во всех jar-файлах поиск дублирующихся классов (поиск по их полному имени, т.е. по имени пакета) :)
Eng.Fouad
Хорошо, используя NetBeans и maven-shade, я думаю, что могу видеть все дублирующиеся классы и файлы jar, в которых они находятся. Многие из этих файлов jar, на которые я не ссылался напрямую в моем pom.xml, поэтому я предполагаю, что они должны быть включенным в мои зависимости. Есть ли простой способ узнать, какая зависимость включает их, не просматривая каждый из pom-файлов зависимостей? (прошу прощения за мою нубишность, я девственница
Ява
@beterthanlife Лучше задайте новый вопрос относительно этого :)
Eng.Fouad
Я нашел дубликат банку, посмотрев на график зависимостей gradle (./gradlew: <имя>: зависимости). Поэтому я нашел виновника, который привлек старую версию библиотеки в проект.
nyx
1

Еще одна ситуация, в которой может появиться эта ошибка, - Эмма-код Покрытие.

Это происходит при назначении объекта интерфейсу. Я предполагаю, что это как-то связано с инструментируемым объектом, который больше не является бинарно-совместимым.

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

К счастью, эта проблема не возникает с Cobertura, поэтому я добавил cobertura-maven-plugin в свои плагины для отчетов моего pom.xml

stivlo
источник
1

Я столкнулся с этой проблемой при развертывании и развертывании войны со стеклянной рыбой. Моя классовая структура была такой,

public interface A{
}

public class AImpl implements A{
}

и это было изменено на

public abstract class A{
}

public class AImpl extends A{
}

После остановки и перезапуска домена все заработало нормально. Я использовал Glassfish 3.1.43

Nerrve
источник
1

У меня есть веб-приложение, которое прекрасно разворачивается на tomcat моей локальной машины (8.0.20). Однако, когда я поместил его в среду qa (tomcat - 8.0.20), он продолжал давать мне IncompatibleClassChangeError, и он жаловался, что я расширяю интерфейс. Этот интерфейс был изменен на абстрактный класс. И я скомпилировал родительские и дочерние классы, и все же продолжал получать ту же проблему. Наконец, я хотел отладить, поэтому я изменил версию на родительском для x.0.1-SNAPSHOT, а затем скомпилировал все, и теперь это работает. Если кто-то все еще сталкивается с проблемой после выполнения ответов, приведенных здесь, убедитесь, что версии в вашем файле pom.xml также верны. Измените версии, чтобы увидеть, работает ли это. Если так, то исправьте проблему с версией.

Джобин Томас
источник
1

Мой ответ, я полагаю, будет конкретным для Интеллидж.

Я восстановил чистый, даже зашел так далеко, что вручную удалил "out" и "target" dirs. Intellij имеет «аннулировать кэши и перезапустить», что иногда удаляет нечетные ошибки. На этот раз это не сработало. Все версии зависимостей выглядят корректно в меню «Настройки проекта» -> «Модули».

Окончательный ответ состоял в том, чтобы вручную удалить мою проблемную зависимость из моего локального репозитория Maven. Преступником была старая версия bouncycastle (я знал, что только что сменил версию, и это будет проблемой), и хотя старая версия не показала, где в том, что строится, она решила мою проблему. Я использовал Intellij версии 14, а затем обновился до 15 во время этого процесса.

Дэвид Никерсон
источник
1

В моем случае я столкнулся с этой ошибкой таким образом. pom.xmlмоего проекта определены две зависимости Aи B. И как Aи Bопределяется зависимость же артефакт (назовем его C) , но различные версии этого ( C.1и C.2). Когда это происходит, для каждого класса в Cmaven можно выбрать только одну версию класса из двух версий (при создании uber-jar ). Он выберет «ближайшую» версию на основе своих правил передачи зависимостей и выдаст предупреждение «У нас есть дублирующийся класс ...». Если сигнатура метода / класса изменяется между версиями, это может вызвать java.lang.IncompatibleClassChangeErrorисключение, если неверная версия используется во время выполнения.

Дополнительно: если Aнеобходимо использовать v1 of Cи Bдолжно использовать v2 of C, то мы должны переместить C в Aи Bpoms, чтобы избежать конфликта классов (у нас есть дублирующееся предупреждение о классе) при построении окончательного проекта, который зависит от обоих Aи B.

Морфей
источник
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

Пожалуйста, проверьте, не состоит ли ваш код из двух проектов модулей с одинаковыми именами классов и определением пакетов. Например, это может произойти, если кто-то использует copy-paste для создания новой реализации интерфейса на основе предыдущей реализации.

denu
источник
0

Если это запись о возможных случаях этой ошибки, то:

Я только что получил эту ошибку на WAS (8.5.0.1), во время загрузки CXF (2.6.0) конфигурации Spring (3.1.1_release), где 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 и выберите клиентские модули. В конфигурации модуля установите загрузку классов как «родительский последний».

Это находится в консоли WAS:

  • Applicatoins -> Типы приложений -> Приложения WebSphere Enterprise
  • Нажмите на ссылку, представляющую ваше заявление (война)
  • Нажмите «Управление модулями» в разделе «Модули»
  • Нажмите на ссылку для базового модуля (ей)
  • Измените «Порядок загрузчика классов» на «(родительский последний)».
wmorrison365
источник
0

Документирование другого сценария после прожигания слишком много времени.

Убедитесь, что у вас нет jar зависимостей, в котором есть класс с аннотацией EJB.

У нас был общий jar-файл с @localаннотацией. Позже этот класс был перенесен из этого общего проекта в наш основной проект ejb jar. Наш ejb jar и наш общий jar связаны в ухо. Версия нашей общей jar-зависимости не была обновлена. Таким образом 2 класса пытаются быть чем-то с несовместимыми изменениями.

Snekse
источник
0

Все вышеперечисленное - по какой-то причине я занимался большим рефакторингом и начинал это понимать. Я переименовал пакет, в котором был мой интерфейс, и это очистило его. Надеюсь, это поможет.

bsautner
источник
0

По какой-то причине то же исключение также выдается при использовании JNI и передаче аргумента jclass вместо jobject при вызове Call*Method().

Это похоже на ответ из Огрского псалма 33.

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.So; по сути, разрешения зависимостей не происходят из-за различных имена, приводящие к ошибкам во время выполнения приложения scala.

sourabh
источник
0

Дополнительная причина этой проблемы - если вы Instant Runвключили Android Studio.

Исправление

Если вы обнаружите, что начинаете получать эту ошибку, выключите Instant Run.

  1. Основные настройки Android Studio
  2. Сборка, выполнение, развертывание
  3. Мгновенный запуск
  4. Снимите флажок «Включить мгновенный запуск ...»

Зачем

Instant Runизменяет большое количество вещей во время разработки, чтобы быстрее предоставлять обновления для вашего работающего приложения. Отсюда мгновенный бег. Когда это работает, это действительно полезно. Однако, когда возникает такая проблема, лучше всего отключить ее Instant Runдо следующей версии Android Studio.

Кносский
источник