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

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

21 ответ

Решение

Это означает, что вы внесли некоторые несовместимые двоичные изменения в библиотеку, не перекомпилировав код клиента. Java Language Specification §13 details all such changes, most prominently, changing non-static non-private fields/methods to be static или наоборот.

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

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". Обновление банок зависимостей в идеале не должно нарушать работу приложения или сборки. If you do have to break binary backward compatibility, it's recommended to increase the major version number (eg from 1.xy to 2.0.0) before releasing the change.

Ваша недавно упакованная библиотека не является обратно совместимой двоичной (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

Удачи!

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

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

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

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

Удачи!

Я также обнаружил, что при использовании 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...
        }
    }
}

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

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

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

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

В моем случае ошибка появилась, когда я добавил 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>

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

Исправление

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

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

Зачем

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

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

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

public interface A{
}

public class AImpl implements A{
}

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

public abstract class A{
}

public class AImpl extends A{
}

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

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

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

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

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

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

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

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

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

В моем случае:

  • У меня есть проект, содержащий несколько модулей, в том числеapp, ,
  • Я создал в модуле приложения.
  • Затем я создал файл в тестовом модуле, файл содержит несколько помощников для созданияOneElementCacheв тестах.
  • До сих пор все работает отлично (обаtestи проходит).
  • После этого я создал файлCacheв модуле приложения.
  • Получил во время бегаintegrationTest:
      Caused by: java.lang.IncompatibleClassChangeError: 
    class app.cache.CacheImpl can not implement app.cache.Cache, because it is not an interface (app.cache.Cache is in unnamed module of loader 'app')

Причиной стал конфликт именования в разных модулях (приложение/тест). Изменение имени файла в тесте помогло.

Если вы пришли из разработки Android. Тогда попробуйтеrebuildвариант может быть исправить для вас.

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

Я только что получил эту ошибку на 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

В этом случае решением было изменить порядок пути к классам модуля в моем файле войны. То есть откройте военное приложение в консоли WAS и выберите клиентские модули. В конфигурации модуля установите для загрузки классов значение "родительский последний".

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

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

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

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

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

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

По какой-то причине то же исключение также выдается при использовании 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 поэтому я хотел задокументировать этот особый случай.

Добавление моих 2 центов. Если вы используете scala и sbt и scala-logging в качестве зависимости, то это может произойти, потому что более ранняя версия scala-logging имела имя scala-logging-api.So; по сути, разрешения зависимостей не происходят из-за различных имена, приводящие к ошибкам во время выполнения приложения scala.

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

Когда я попытался создать экземпляр класса, сгенерированного байт-кодом, JVM пожаловался на java.lang.IncompatibleClassChangeError.

К счастью, у исключения есть элемент «сообщение», который предоставляет более подробную информацию о том, что пошло не так. В моем случае в сообщении четко говорилось, что конкретный класс должен был реализовать конкретный интерфейс, но на самом деле он его не реализовывал.

Другие вопросы по тегам