Apa yang menyebabkan java.lang.IncompatibleClassChangeError?

218

Saya mengemas perpustakaan Java sebagai JAR, dan itu membuang banyak java.lang.IncompatibleClassChangeErrorketika saya mencoba untuk memanggil metode dari itu. Kesalahan ini tampaknya muncul secara acak. Jenis masalah apa yang dapat menyebabkan kesalahan ini?

Zombie
sumber
Dalam proyek Eclipse di mana saya menguji Apache FOP 1.0 dan Barcode4J, pustaka tambahan yang datang dengan Barcode4J tampaknya menimpa yang datang dengan FOP (beberapa memiliki nomor versi yang lebih tinggi). Ini adalah kasus untuk sangat berhati-hati dengan apa yang Anda masukkan ke path / classpath Anda.
Wivani

Jawaban:

170

Ini berarti bahwa Anda telah membuat beberapa perubahan biner yang tidak kompatibel ke pustaka tanpa mengkompilasi ulang kode klien. Spesifikasi Bahasa Jawa §13 merinci semua perubahan seperti itu, yang paling jelas, mengubah staticbidang / metode non-pribadi menjadi staticatau sebaliknya.

Kompilasi ulang kode klien terhadap pustaka baru, dan Anda harus melakukannya dengan baik.

UPDATE: Jika Anda menerbitkan perpustakaan umum, Anda harus menghindari membuat perubahan biner yang tidak kompatibel sebanyak mungkin untuk mempertahankan apa yang dikenal sebagai "kompatibilitas mundur biner". Memperbarui stoples ketergantungan saja idealnya tidak merusak aplikasi atau build. Jika Anda harus memutus kompatibilitas mundur biner, disarankan untuk menambah nomor versi utama (mis. Dari 1.xy ke 2.0.0) sebelum merilis perubahan.

tidak ada
sumber
2
Untuk beberapa alasan, pengembang di sini mengalami masalah ketika mengkompilasi ulang kode klien tidak memperbaiki masalah dengan tepat. Untuk beberapa alasan jika mereka mengedit file di mana itu terjadi dan mengkompilasi ulang kesalahan tidak lagi terjadi di sana, tetapi lebih banyak akan muncul secara acak di tempat lain dalam proyek, di mana perpustakaan direferensikan. Saya ingin tahu apa yang bisa menyebabkan ini.
Zombies
5
Sudahkah Anda mencoba melakukan build bersih (menghapus semua *.classfile) dan mengkompilasi ulang? Mengedit file memiliki efek yang serupa.
notnoop
Tidak ada kode yang dihasilkan secara dinamis .... kecuali jika Anda menganggap JSP sebagai kode tersebut. Kami memang mencoba menghapus file kelas dan ini sepertinya tidak membantu. Yang aneh adalah bahwa hal itu sepertinya tidak terjadi pada saya tetapi itu terjadi pada pengembang lainnya.
Zombies
2
Bisakah Anda memastikan bahwa ketika Anda melakukan build bersih Anda mengkompilasi terhadap guci yang sama dengan yang Anda jalankan?
notnoop
Apakah ada sesuatu yang berkaitan dengan platform 32bit atau 64bit, saya menemukan kesalahan hanya tampil di platform 64bit.
cowboi-peng
96

Pustaka Anda yang baru dikemas tidak kompatibel dengan backward binary (BC) dengan versi lama. Untuk alasan ini beberapa klien perpustakaan yang tidak dikompilasi ulang dapat melempar pengecualian.

Ini adalah daftar lengkap perubahan dalam Java library API yang dapat menyebabkan klien yang dibuat dengan versi lama dari perpustakaan untuk membuang java.lang. IncompatibleClassChangeError jika mereka berjalan pada yang baru (yaitu melanggar BC):

  1. Bidang non-final menjadi statis,
  2. Bidang non-konstan menjadi non-statis,
  3. Kelas menjadi antarmuka,
  4. Antarmuka menjadi kelas,
  5. jika Anda menambahkan bidang baru ke kelas / antarmuka (atau menambahkan kelas-super / antarmuka-super baru) maka bidang statis dari antarmuka-super dari kelas klien C dapat menyembunyikan bidang yang ditambahkan (dengan nama yang sama) yang diwarisi dari kelas super C (sangat jarang terjadi).

Catatan : Ada banyak pengecualian lain yang disebabkan oleh perubahan lain yang tidak kompatibel: NoSuchFieldError , NoSuchMethodError , IllegalAccessError , InstantiationError , VerifyError , NoClassDefFoundError dan AbstractMethodError .

Makalah yang lebih baik tentang BC adalah "Evolving API berbasis Java 2: Mencapai Kompatibilitas Biner API" yang ditulis oleh Jim des Rivières.

Ada juga beberapa alat otomatis untuk mendeteksi perubahan tersebut:

Penggunaan checker kepatuhan japi untuk perpustakaan Anda:

japi-compliance-checker OLD.jar NEW.jar

Penggunaan alat clirr:

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

Semoga berhasil!

linuxbuild
sumber
59

Meskipun semua jawaban ini benar, menyelesaikan masalah seringkali lebih sulit. Ini umumnya merupakan hasil dari dua versi yang agak berbeda dari ketergantungan yang sama pada classpath, dan hampir selalu disebabkan oleh superclass yang berbeda daripada yang semula dikompilasi agar tidak berada di classpath atau beberapa impor dari transitive closure yang berbeda, tetapi umumnya di kelas pemanggilan instantiasi dan konstruktor. (Setelah pemuatan kelas berhasil dan pemanggilan aktor, Anda akan mendapatkan NoSuchMethodExceptionatau yang lainnya.)

Jika perilaku tampak acak, kemungkinan hasil dari program multithreaded classloading dependensi transitif yang berbeda berdasarkan kode apa yang terkena pertama.

Untuk mengatasinya, coba luncurkan VM dengan -verbosesebagai argumen, lalu lihat kelas yang sedang dimuat saat pengecualian terjadi. Anda harus melihat beberapa informasi yang mengejutkan. Misalnya, memiliki banyak salinan dengan ketergantungan dan versi yang sama yang tidak pernah Anda harapkan atau akan Anda terima jika Anda tahu mereka dimasukkan.

Menyelesaikan guci duplikat dengan Maven paling baik dilakukan dengan kombinasi maven-dependency-plugin dan maven-penegakan-plugin di bawah Maven (atau SBT's Dependency Graph Plugin , kemudian menambahkan guci-guci itu ke bagian POM tingkat atas Anda atau sebagai ketergantungan impor) elemen dalam SBT (untuk menghapus dependensi itu).

Semoga berhasil!

Brian Topping
sumber
5
Argumen verbose membantu saya menentukan stoples mana yang memberikan masalah. Terima kasih
Virat Kadaru
6

Saya juga menemukan bahwa, ketika menggunakan JNI, memanggil metode Java dari C ++, jika Anda meneruskan parameter ke metode Java yang dipanggil dalam urutan yang salah, Anda akan mendapatkan kesalahan ini ketika Anda mencoba menggunakan parameter di dalam metode yang disebut (karena mereka tidak akan menjadi tipe yang tepat). Saya awalnya terkejut bahwa JNI tidak melakukan pemeriksaan ini untuk Anda sebagai bagian dari pemeriksaan tanda tangan kelas ketika Anda memanggil metode, tetapi saya berasumsi mereka tidak melakukan pemeriksaan semacam ini karena Anda mungkin melewati parameter polimorfik dan mereka harus menganggap Anda tahu apa yang Anda lakukan.

Contoh C ++ JNI Code:

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

Contoh Kode 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 Psalm33
sumber
1
Terima kasih atas petunjuknya. Saya memiliki masalah yang sama ketika memanggil metode Java dengan contoh yang salah (ini) disediakan.
sstn
5

Saya memiliki masalah yang sama, dan kemudian saya menemukan bahwa saya menjalankan aplikasi pada Java versi 1.4 sementara aplikasi dikompilasi pada versi 6.

Sebenarnya, alasannya adalah memiliki perpustakaan duplikat, satu terletak di dalam classpath dan yang lainnya dimasukkan ke dalam file jar yang terletak di dalam classpath.

Eng.Fouad
sumber
Bagaimana Anda sampai ke bagian bawah ini? Saya yakin saya memiliki masalah yang sama tetapi tidak memiliki petunjuk bagaimana cara melacak pustaka dependensi mana yang sedang diduplikasi.
beterthanlife
@beterthanlife menulis skrip yang mencari di dalam semua file jar mencari kelas yang digandakan (cari dengan nama lengkap mereka, yaitu dengan nama paket) :)
Eng.Fouad
ok, menggunakan NetBeans dan maven-shade saya pikir saya bisa melihat semua kelas yang sedang diduplikasi dan file jar mereka berada. Banyak file jar ini saya tidak secara langsung dirujuk di pom.xml saya, jadi saya menganggap mereka harus dimasukkan oleh dependensi saya. Apakah ada cara mudah untuk mengetahui dependensi mana yang termasuk mereka, tanpa melalui masing-masing file pom dependensi? (maafkan noobishness saya, saya seorang perawan java!)
beterthanlife
@beterthanlife Lebih baik ajukan pertanyaan baru mengenai hal itu :)
Eng.Fouad
Saya menemukan tabung duplikat dengan melihat grafik dependensi gradle (./gradlew: <name>: dependencies). Jadi saya menemukan pelakunya yang menarik versi lama lib ke dalam proyek.
nyx
1

Situasi lain di mana kesalahan ini dapat muncul adalah dengan Cakupan Kode Emma.

Ini terjadi ketika menetapkan Obyek ke antarmuka. Saya kira ini ada hubungannya dengan Objek yang diinstrumentasi dan tidak kompatibel biner lagi.

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

Untungnya masalah ini tidak terjadi pada Cobertura, jadi saya telah menambahkan cobertura-maven-plugin di plugin pelaporan pom.xml saya

stivlo
sumber
1

Saya telah menghadapi masalah ini saat menganggur dan memindahkan perang dengan glassfish. Struktur kelas saya seperti ini,

public interface A{
}

public class AImpl implements A{
}

dan diubah menjadi

public abstract class A{
}

public class AImpl extends A{
}

Setelah berhenti dan memulai kembali domain, itu berhasil dengan baik. Saya menggunakan glassfish 3.1.43

Nerrve
sumber
1

Saya memiliki aplikasi web yang menyebarkan baik-baik saja pada kucing jantan mesin lokal saya (8.0.20). Namun, ketika saya memasukkannya ke dalam lingkungan qa (kucing jantan - 8.0.20), ia terus memberi saya IncompatibleClassChangeError dan itu mengeluh bahwa saya memperluas pada antarmuka. Antarmuka ini diubah menjadi kelas abstrak. Dan saya mengkompilasi kelas orang tua dan anak dan masih terus mendapatkan masalah yang sama. Akhirnya, saya ingin men-debug, jadi, saya mengubah versi pada induknya ke x.0.1-SNAPSHOT dan kemudian mengkompilasi semuanya dan sekarang sudah berfungsi. Jika seseorang masih mengalami masalah setelah mengikuti jawaban yang diberikan di sini, pastikan versi di pom.xml Anda juga benar. Ubah versi untuk melihat apakah itu berfungsi. Jika demikian, maka perbaiki masalah versi.

Jobin Thomas
sumber
1

Jawaban saya, saya yakin, akan spesifik untuk Intellij.

Saya telah membangun kembali bersih, bahkan sejauh secara manual menghapus dir "keluar" dan "target". Intellij memiliki "cache tidak valid dan mulai ulang", yang terkadang menghapus kesalahan aneh. Kali ini tidak berhasil. Semua versi ketergantungan terlihat benar di menu pengaturan-> modul modul.

Jawaban terakhir adalah secara manual menghapus ketergantungan masalah saya dari repo pakar lokal saya. Versi lama bouncycastle adalah biang keladinya (saya tahu saya baru saja mengubah versi dan itu akan menjadi masalah) dan meskipun versi lama tidak menunjukkan di mana dalam apa yang sedang dibangun, itu menyelesaikan masalah saya. Saya menggunakan intellij versi 14 dan kemudian ditingkatkan menjadi 15 selama proses ini.

David Nickerson
sumber
1

Dalam kasus saya, saya mengalami kesalahan ini dengan cara ini. pom.xmlproyek saya mendefinisikan dua dependensi Adan B. Dan keduanya Adan Bketergantungan yang ditentukan pada artefak yang sama (menyebutnya C) tetapi versi yang berbeda ( C.1dan C.2). Ketika ini terjadi, untuk setiap kelas di Cpakar hanya dapat memilih satu versi kelas dari dua versi (sambil membangun uber-jar ). Ini akan memilih versi "terdekat" berdasarkan aturan mediasi dependensi dan akan menampilkan peringatan "Kami memiliki kelas duplikat ..." Jika metode / tanda tangan kelas berubah di antara versi, itu dapat menyebabkan java.lang.IncompatibleClassChangeErrorpengecualian jika versi yang salah adalah digunakan saat runtime.

Lanjutan: Jika Aharus menggunakan v1 dari Cdan Bharus menggunakan v2 dari C, maka kita harus pindah C di Adan B's pom konflik kelas menghindari (kami memiliki peringatan kelas duplikat) ketika membangun proyek akhir yang tergantung pada kedua Adan B.

Morpheus
sumber
1

Dalam kasus saya kesalahan muncul ketika saya menambahkan com.nimbusdsperpustakaan di aplikasi saya yang digunakan Websphere 8.5.
Pengecualian di bawah ini terjadi:

Disebabkan oleh: java.lang.IncompatibleClassChangeError: org.objectweb.asm.AnnotationVisitor

Solusinya adalah dengan mengecualikan tabung asm dari perpustakaan:

<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
sumber
0

Silakan periksa apakah kode Anda tidak terdiri dari dua proyek modul yang memiliki nama kelas dan definisi paket yang sama. Misalnya ini bisa terjadi jika seseorang menggunakan copy-paste untuk membuat implementasi antarmuka baru berdasarkan implementasi sebelumnya.

denu
sumber
0

Jika ini merupakan catatan kemungkinan terjadinya kesalahan ini, maka:

Saya baru saja mendapatkan kesalahan ini pada WS (8.5.0.1), selama pemuatan CXF (2.6.0) dari pegas (3.1.1_release) konfigurasi di mana BeanInstantiationException menggulung CXF ExtensionException, menggulung IncompatibleClassChangeError. Cuplikan berikut ini menunjukkan inti dari jejak tumpukan:

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

Dalam hal ini, solusinya adalah mengubah urutan classpath dari modul dalam file perang saya. Yaitu, buka aplikasi perang di konsol WS di bawah dan pilih modul klien. Dalam konfigurasi modul, atur pemuatan kelas menjadi "induk terakhir".

Ini ditemukan di konsol WS:

  • Applicatoins -> Jenis Aplikasi -> Aplikasi WebSphere Enterprise
  • Klik tautan yang mewakili aplikasi Anda (perang)
  • Klik "Kelola Modul" di bawah bagian "Modul"
  • Klik tautan untuk modul yang mendasarinya
  • Ubah "Urutan pemuat kelas" menjadi "(terakhir induk)".
wmorrison365
sumber
0

Mendokumentasikan skenario lain setelah membakar terlalu banyak waktu.

Pastikan Anda tidak memiliki jar ketergantungan yang memiliki kelas dengan anotasi EJB di atasnya.

Kami memiliki file jar umum yang memiliki @localanotasi. Kelas itu kemudian dipindahkan dari proyek bersama itu ke proyek ejb jar utama kami. Guci ejb kami dan guci umum kami keduanya dibundel di dalam telinga. Versi ketergantungan botol umum kami tidak diperbarui. Jadi 2 kelas berusaha menjadi sesuatu dengan perubahan yang tidak kompatibel.

Snekse
sumber
0

Semua hal di atas - untuk alasan apa pun saya sedang melakukan refactor besar dan mulai mendapatkan ini. Saya mengganti nama paket dengan antarmuka saya dan itu membersihkannya. Semoga itu bisa membantu.

bsautner
sumber
0

Untuk beberapa alasan pengecualian yang sama juga dilemparkan ketika menggunakan JNI dan melewati argumen jclass bukannya pada jobject saat memanggil a Call*Method().

Ini mirip dengan jawaban dari 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;
}

Saya tahu ini agak terlambat untuk menjawab pertanyaan ini 5 tahun setelah ditanya tetapi ini adalah salah satu hit teratas ketika mencari java.lang.IncompatibleClassChangeErrorjadi saya ingin mendokumentasikan kasus khusus ini.

Eric Obermühlner
sumber
0

Menambahkan 2 sen saya. Jika Anda menggunakan scala dan sbt dan scala-logging sebagai dependensi, maka ini dapat terjadi karena versi scala-logging sebelumnya memiliki nama scala-logging-api.So, pada dasarnya resolusi dependensi tidak terjadi karena perbedaan nama-nama yang mengarah ke kesalahan runtime saat meluncurkan aplikasi scala.

sourabh
sumber
0

Penyebab tambahan masalah ini, adalah jika Anda telah Instant Runmengaktifkan untuk Android Studio.

Cara mengatasinya

Jika Anda mulai mendapatkan kesalahan ini, matikan Instant Run.

  1. Pengaturan utama Android Studio
  2. Bangun, Eksekusi, Penempatan
  3. Jalankan Instan
  4. Hapus centang "Aktifkan lari instan ..."

Mengapa

Instant Runmemodifikasi sejumlah besar hal selama pengembangan, untuk membuatnya lebih cepat untuk memberikan pembaruan ke Aplikasi Anda yang sedang berjalan. Karena itu jalankan instan. Ketika berhasil, itu sangat berguna. Namun, ketika masalah seperti ini menyerang, hal terbaik untuk dilakukan adalah mematikan Instant Runversi Android Studio berikutnya.

Knossos
sumber