Co powoduje java.lang.IncompatibleClassChangeError?

218

Pakuję bibliotekę Java jako plik JAR i rzuca wiele java.lang.IncompatibleClassChangeErrors, gdy próbuję wywołać z niej metody. Te błędy wydają się pojawiać losowo. Jakie problemy mogą powodować ten błąd?

Zombie
źródło
W projekcie Eclipse, w którym testowałem Apache FOP 1.0 i Barcode4J, dodatkowe biblioteki dostarczone z Barcode4J najwyraźniej przesłaniały te, które były dostarczane z FOP (niektóre miały wyższe numery wersji). Jest to powód do zachowania ostrożności podczas umieszczania ścieżki kompilacji / ścieżki klas.
Wivani

Odpowiedzi:

170

Oznacza to, że dokonałeś pewnych niekompatybilnych zmian binarnych w bibliotece bez ponownej kompilacji kodu klienta. Specyfikacja języka Java §13 wyszczególnia wszystkie takie zmiany, w sposób najbardziej widoczny, zmieniając nieprywatne staticpola / metody na staticlub odwrotnie.

Ponownie skompiluj kod klienta względem nowej biblioteki i powinieneś być gotowy.

AKTUALIZACJA: Jeśli publikujesz bibliotekę publiczną, powinieneś unikać dokonywania możliwie niekompatybilnych zmian binarnych, aby zachować tak zwaną „binarną kompatybilność wsteczną”. Samo aktualizowanie słoików zależności nie powinno uszkodzić aplikacji ani kompilacji. Jeśli musisz przerwać binarną kompatybilność wsteczną, zaleca się zwiększenie głównego numeru wersji (np. Z 1.xy do 2.0.0) przed wydaniem zmiany.

notnoop
źródło
2
Z jakiegoś powodu tutaj programiści mają problem polegający na tym, że rekompilacja kodu klienta nie rozwiązuje problemu dokładnie. Z jakiegoś powodu, jeśli edytują plik, w którym się pojawia i ponownie kompilują, błąd już tam nie występuje, ale więcej losowo wyskakuje w innym miejscu w projekcie, do którego odwołuje się biblioteka. Jestem ciekawy, co może być przyczyną tego.
Zombie
5
Czy próbowałeś wykonać czystą kompilację (usunąć wszystkie *.classpliki) i ponownie skompilować? Edytowanie plików ma podobny efekt.
notnoop
Brak dynamicznie generowanego kodu .... chyba że uważasz JSP za taki. Próbowaliśmy usunąć pliki klas, ale to nie pomogło. Dziwne jest to, że po prostu nie wydaje mi się, ale zdarza się to w przypadku innych programistów.
Zombie
2
Czy możesz zapewnić, że wykonując czystą kompilację, kompilujesz na tych samych słoikach, z którymi korzystasz?
notnoop,
Czy jest coś związanego z platformą 32-bitową lub 64-bitową, znalazłem błąd występujący tylko na platformie 64-bitowej.
cowboi-peng
96

Twoja nowo spakowana biblioteka nie jest wstecznie kompatybilna binarnie (BC) ze starą wersją. Z tego powodu niektórzy klienci biblioteki, którzy nie zostali ponownie skompilowani, mogą zgłosić wyjątek.

Jest to pełna lista zmian w interfejsie API biblioteki Java, które mogą spowodować, że klienci zbudowani ze starej wersji biblioteki wyrzucą java.lang. IncompatibleClassChangeError, jeśli działają na nowym (tzn. Zepsują BC):

  1. Pole niefinalne staje się statyczne,
  2. Pole niestałe staje się niestatyczne,
  3. Klasa staje się interfejsem,
  4. Interfejs staje się klasą,
  5. jeśli dodasz nowe pole do klasy / interfejsu (lub dodasz nową superklasę / super-interfejs), wówczas pole statyczne z super-interfejsu klasy klienta C może ukryć dodane pole (o tej samej nazwie) odziedziczone z superklasa C (bardzo rzadki przypadek).

Uwaga : Istnieje wiele innych wyjątków spowodowanych innymi niezgodnymi zmianami: NoSuchFieldError , NoSuchMethodError , IllegalAccessError , InstantiationError , VerifyError , NoClassDefFoundError i AbstractMethodError .

Lepszym tekstem na temat BC jest „Ewolucja API opartych na Javie 2: Osiąganie zgodności binarnej API” napisana przez Jima des Rivières.

Istnieją również automatyczne narzędzia do wykrywania takich zmian:

Zastosowanie sprawdzania zgodności japi dla twojej biblioteki:

japi-compliance-checker OLD.jar NEW.jar

Zastosowanie narzędzia Clirr:

java -jar clirr-core-0.6-uber.jar -o OLD.jar -n NEW.jar

Powodzenia!

linuxbuild
źródło
59

Chociaż wszystkie te odpowiedzi są poprawne, rozwiązanie problemu jest często trudniejsze. Zasadniczo wynika to z dwóch nieznacznie różnych wersji tej samej zależności od ścieżki klasy i prawie zawsze jest spowodowana albo inną nadklasą, niż pierwotnie skompilowano, że nie jest na ścieżce klasy lub inny import zamknięcia przechodniego jest inny, ale ogólnie w klasie tworzenie instancji i wywoływanie konstruktora. (Po udanym załadowaniu klasy i wywołaniu ctor dostaniesz NoSuchMethodExceptionlub coś takiego .)

Jeśli zachowanie wydaje się losowe, prawdopodobnie jest to wynikiem działania wielowątkowej klasy programu ładującej różne zależności przechodnie na podstawie tego, jaki kod został trafiony jako pierwszy.

Aby rozwiązać ten problem, spróbuj uruchomić maszynę wirtualną z -verboseargumentem, a następnie spójrz na klasy, które były ładowane, gdy wystąpi wyjątek. Powinieneś zobaczyć zaskakujące informacje. Na przykład posiadanie wielu kopii tej samej zależności i wersji, których nigdy się nie spodziewałeś lub zaakceptowałbyś, gdybyś wiedział, że są one uwzględnione.

Rozwiązanie zduplikowanych słoików za pomocą Maven najlepiej wykonać za pomocą kombinacji wtyczki zależnej od maven i wtyczki maven-enforcer w Maven (lub SBT's Dependency Graph Plugin) , a następnie dodając te słoiki do sekcji POM najwyższego poziomu lub jako zależność importowaną elementy w SBT (aby usunąć te zależności).

Powodzenia!

Brian Topping
źródło
5
Pełen argumentów pomógł mi ustalić, które słoiki powodują problemy. Dzięki
Virat Kadaru,
6

Odkryłem również, że podczas używania JNI, wywoływania metody Java z C ++, jeśli przekażesz parametry do wywoływanej metody Java w niewłaściwej kolejności, pojawi się ten błąd, gdy spróbujesz użyć parametrów wewnątrz wywoływanej metody (ponieważ nie będzie odpowiednim typem). Początkowo byłem zaskoczony, że JNI nie wykonuje tego sprawdzania w ramach sprawdzania podpisu klasy podczas wywoływania metody, ale zakładam, że nie robią tego rodzaju sprawdzania, ponieważ możesz przekazywać parametry polimorficzne i muszą Załóżmy, że wiesz, co robisz.

Przykładowy kod JNI C ++:

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

Przykładowy kod 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...
        }
    }
}
Ogre Psalm 33
źródło
1
Dzięki za podpowiedź. Miałem ten sam problem podczas wywoływania metody Java z podaną nieprawidłową instancją (this).
sstn
5

Miałem ten sam problem, a później zorientowałem się, że uruchamiam aplikację w Javie w wersji 1.4, podczas gdy aplikacja jest kompilowana w wersji 6.

W rzeczywistości przyczyną było posiadanie zduplikowanej biblioteki, jedna znajduje się w ścieżce klasy, a druga znajduje się w pliku jar, który znajduje się w ścieżce klasy.

Eng.Fouad
źródło
Jak doszedłeś do sedna tego? Jestem pewien, że mam podobny problem, ale nie mam pojęcia, jak prześledzić, która biblioteka zależności jest duplikowana.
beterthanlife
@beterthanlife napisz skrypt, który przeszukuje wszystkie pliki jar w poszukiwaniu zduplikowanych klas (wyszukaj według ich pełnej nazwy, tj. z nazwą pakietu) :)
Eng.Fouad
ok, używając NetBeans i maven-shadow myślę, że widzę wszystkie klasy, które są duplikowane, i pliki jar, w których się znajdują. być uwzględnionym przez moje zależności. Czy istnieje prosty sposób, aby dowiedzieć się, która zależność je uwzględnia, bez konieczności przeglądania pliku pom każdego z tych zależności? (proszę wybaczyć moją noobishness, jestem dziewicą z
Jawy
@beterthanlife Lepiej zadaj nowe pytanie na ten temat :)
Eng.Fouad
Znalazłem zduplikowany słoik, patrząc na wykres zależności między stopniami (./gradlew: <nazwa>: zależności). Znalazłem więc winowajcę, który wciągnął starą wersję biblioteki lib do projektu.
nyx
1

Inną sytuacją, w której może pojawić się ten błąd, jest usługa Emma Code Coverage.

Dzieje się tak podczas przypisywania obiektu do interfejsu. Wydaje mi się, że ma to coś wspólnego z instrumentowanym Obiektem i nie jest kompatybilny binarnie.

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

Na szczęście ten problem nie występuje w Cobertura, więc dodałem wtyczkę cobertura-maven-plugin do moich wtyczek raportujących mojej pom.xml

stivlo
źródło
1

Napotkałem ten problem podczas cofania i ponownego wdrażania wojny ze szklistymi rybami. Moja struktura klasowa była taka,

public interface A{
}

public class AImpl implements A{
}

i został zmieniony na

public abstract class A{
}

public class AImpl extends A{
}

Po zatrzymaniu i ponownym uruchomieniu domeny wszystko poszło dobrze. Używałem szklanych ryb 3.1.43

Nerrve
źródło
1

Mam aplikację internetową, która doskonale się wdraża na tomcat mojego komputera lokalnego (8.0.20). Jednak, kiedy umieściłem go w środowisku qa (tomcat - 8.0.20), nadal dawał mi IncompatibleClassChangeError i narzekałem, że rozszerzam interfejs. Ten interfejs został zmieniony na klasę abstrakcyjną. I skompilowałem klasy dla rodziców i dzieci i wciąż otrzymywałem ten sam problem. Wreszcie chciałem debugować, więc zmieniłem wersję nadrzędną na x.0.1-SNAPSHOT, a następnie skompilowałem wszystko i teraz działa. Jeśli ktoś nadal napotyka problem po wykonaniu podanych tutaj odpowiedzi, upewnij się, że wersje w pliku pom.xml są również poprawne. Zmień wersje, aby zobaczyć, czy to działa. Jeśli tak, napraw problem z wersją.

Jobin Thomas
źródło
1

Wierzę, że moja odpowiedź będzie specyficzna dla Intellij.

Odbudowałem czysty, nawet posuwam się do ręcznego usunięcia katalogów „out” i „target”. Intellij ma funkcję „unieważniania pamięci podręcznej i restartowania”, która czasami usuwa dziwne błędy. Tym razem to nie zadziałało. Wszystkie wersje zależności wyglądały poprawnie w menu Ustawienia projektu-> moduły.

Ostateczną odpowiedzią było ręczne usunięcie mojej zależności problemu od mojego lokalnego repozytorium maven. Winowajcą była stara wersja bouncycastle (wiedziałem, że właśnie zmieniłem wersje i to byłby problem) i chociaż stara wersja nie pokazywała, gdzie jest budowana, rozwiązała mój problem. Podczas tego procesu korzystałem z wersji Intellij 14, a następnie zaktualizowałem ją do 15.

David Nickerson
źródło
1

W moim przypadku napotkałem ten błąd w ten sposób. pom.xmlmojego projektu zdefiniował dwie zależności Ai B. I oba Ai Bokreślono zależność od samego artefaktu (nazywają to C), ale w różnych wersjach nim ( C.1i C.2). Kiedy tak się dzieje, dla każdej klasy w Cmaven można wybrać tylko jedną wersję klasy z dwóch wersji (podczas budowania słoika ). Wybierze „najbliższą” wersję na podstawie swoich reguł mediacji zależności i wyświetli ostrzeżenie „Mamy zduplikowaną klasę ...” Jeśli podpis metody / klasy zmienia się między wersjami, może to spowodować java.lang.IncompatibleClassChangeErrorwyjątek, jeśli niepoprawna wersja jest używane w czasie wykonywania.

Zaawansowane: Jeśli Amuszą używać V1 Ci Bmusi korzystać z v2 C, to musimy przeprowadzić C w Ai B„s poms do konfliktu klasowego unikać (mamy duplikat klasy ostrzeżenie) przy tworzeniu ostatecznego projektu, który zależy zarówno Ai B.

Morfeusz
źródło
1

W moim przypadku błąd pojawił się, gdy dodałem com.nimbusdsbibliotekę do mojej aplikacji wdrożonej na Websphere 8.5.
Wystąpił następujący wyjątek:

Przyczyna: java.lang.IncompatibleClassChangeError: org.objectweb.asm.AnnotationVisitor

Rozwiązaniem było wykluczenie słoika asm z biblioteki:

<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>
amina
źródło
0

Sprawdź, czy twój kod nie składa się z dwóch projektów modułów, które mają takie same nazwy klas i definicje pakietów. Może się to na przykład zdarzyć, jeśli ktoś użyje funkcji kopiuj-wklej, aby utworzyć nową implementację interfejsu w oparciu o poprzednią implementację.

denu
źródło
0

Jeśli jest to zapis możliwych wystąpień tego błędu, wówczas:

Właśnie dostałem ten błąd na WAS (8.5.0.1), podczas ładowania CXF (2.6.0) konfiguracji sprężyny (3.1.1_release), w której wyjątek BeanInstantiationException zwrócił wyjątek CXF ExtensionException, zwinięcie IncompatibleClassChangeError. Poniższy fragment pokazuje istotę śladu stosu:

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

W tym przypadku rozwiązaniem była zmiana kolejności ścieżek klas modułu w moim pliku wojny. Oznacza to, że otwórz aplikację wojenną w konsoli WAS pod i wybierz moduły klienta. W konfiguracji modułu ustaw ładowanie klasy na „nadrzędny jako ostatni”.

Można to znaleźć w konsoli WAS:

  • Applicatoins -> Typy aplikacji -> Aplikacje WebSphere Enterprise
  • Kliknij link reprezentujący twoją aplikację (wojna)
  • Kliknij „Zarządzaj modułami” w sekcji „Moduły”
  • Kliknij link do podstawowych modułów
  • Zmień „Kolejność modułu ładującego klasy” na „(ostatni nadrzędny)”.
wmorrison365
źródło
0

Dokumentowanie innego scenariusza po spaleniu zdecydowanie za dużo czasu.

Upewnij się, że nie masz jar zależności, który ma klasę z adnotacją EJB.

Mieliśmy wspólny plik jar zawierający @localadnotację. Ta klasa została później przeniesiona z tego wspólnego projektu do naszego głównego projektu słoika ejb. Nasz słoik ejb i wspólny słoik są umieszczone w jednym uchu. Wersja naszej wspólnej zależności jar nie została zaktualizowana. Zatem 2 klasy próbują być czymś z niezgodnymi zmianami.

Snekse
źródło
0

Wszystkie powyższe - z jakiegokolwiek powodu robiłem jakiś duży refaktor i zacząłem to otrzymywać. Zmieniłem nazwę pakietu, w którym znajdował się mój interfejs, i to go wyczyściło. Mam nadzieję, że to pomaga.

bsautner
źródło
0

Z jakiegoś powodu ten sam wyjątek jest zgłaszany również przy użyciu JNI i przekazaniu argumentu jclass zamiast zadania zadania podczas wywoływania a Call*Method().

Jest to podobne do odpowiedzi z 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;
}

Wiem, że trochę za późno jest odpowiedzieć na to pytanie 5 lat po zadaniu pytania, ale jest to jeden z największych hitów podczas wyszukiwania, java.lang.IncompatibleClassChangeErrorwięc chciałem udokumentować ten szczególny przypadek.

Eric Obermühlner
źródło
0

Dodając moje 2 centy. Jeśli używasz scala i sbt i logowania jako zależności, może się to zdarzyć, ponieważ wcześniejsza wersja scala-logingu miała nazwę scala-logging-api. Tak więc, zasadniczo, rozwiązania zależności nie występują z powodu różnych nazwy prowadzące do błędów w czasie wykonywania podczas uruchamiania aplikacji Scala.

sourabh
źródło
0

Dodatkową przyczyną tego problemu jest Instant Runwłączenie Android Studio.

Poprawka

Jeśli zauważysz, że zaczynasz otrzymywać ten błąd, wyłącz go Instant Run.

  1. Główne ustawienia Android Studio
  2. Kompilacja, wykonanie, wdrożenie
  3. Natychmiastowy bieg
  4. Odznacz „Włącz natychmiastowe uruchamianie ...”

Czemu

Instant Runmodyfikuje wiele rzeczy podczas programowania, aby przyspieszyć dostarczanie aktualizacji do uruchomionej aplikacji. Stąd natychmiastowy bieg. Kiedy to działa, jest naprawdę przydatne. Jednak gdy pojawia się taki problem, najlepiej jest go wyłączyć Instant Rundo czasu wydania kolejnej wersji Androida Studio.

Knossos
źródło