Cosa causa java.lang.IncompatibleClassChangeError?

Sto impacchettando una libreria Java come JAR, e lancio molti java.lang.IncompatibleClassChangeError s quando provo a invocare metodi da esso. Questi errori sembrano apparire a caso. Quali tipi di problemi potrebbero causare questo errore?

Ciò significa che hai apportato alcune modifiche binarie incompatibili alla libreria senza ricompilare il codice client. Specifica del linguaggio Java §13 descrive tutte queste modifiche, in particolare, cambiando i campi / metodi non static non privati ​​come static o viceversa.

Ricompila il codice client con la nuova libreria e dovresti essere pronto.

AGGIORNAMENTO: Se pubblichi una libreria pubblica, dovresti evitare di apportare modifiche binarie incompatibili il più ansible per preservare la cosiddetta “compatibilità con le versioni precedenti binarie”. Aggiornare idealmente i jar di dipendenza da soli non dovrebbe interrompere l’applicazione o la build. Se è necessario interrompere la compatibilità con i file binari, si consiglia di aumentare il numero di versione principale (ad esempio da 1.xy a 2.0.0) prima di rilasciare la modifica.

La tua nuova libreria impacchettata non è compatibile con i binari a ritroso (BC) con la vecchia versione. Per questo motivo, alcuni dei client della libreria che non vengono ricompilati possono generare un’eccezione.

Questo è un elenco completo di modifiche nell’API della libreria Java che potrebbe causare l’aggiunta di java.lang ai client creati con una versione precedente della libreria. IncompatibleClassChangeError se vengono eseguiti su uno nuovo (ovvero interrompendo BC):

  1. Il campo non finale diventa statico,
  2. Il campo non costante diventa non statico,
  3. La class diventa interfaccia,
  4. L’interfaccia diventa class,
  5. se aggiungi un nuovo campo alla class / interfaccia (o aggiungi una nuova super-class / super-interfaccia), un campo statico da una super-interfaccia di una class client C può hide un campo aggiunto (con lo stesso nome) ereditato dal super-class di C (caso molto raro).

Nota : esistono molte altre eccezioni causate da altre modifiche incompatibili: NoSuchFieldError , NoSuchMethodError , IllegalAccessError , InstantiationError , VerifyError , NoClassDefFoundError e AbstractMethodError .

Il documento migliore su BC è “Evolving Java-based APIs 2: Achieving API Binary Compatibility” scritto da Jim des Rivières.

Esistono anche alcuni strumenti automatici per rilevare tali modifiche:

  • JAPI-compliance checker
  • clirr
  • japitools
  • sigtest
  • JAPI-checker

Utilizzo di japi-control-checker per la tua libreria:

 japi-compliance-checker OLD.jar NEW.jar 

Utilizzo dello strumento Clirr:

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

In bocca al lupo!

Mentre queste risposte sono tutte corrette, la risoluzione del problema è spesso più difficile. Generalmente è il risultato di due versioni leggermente diverse della stessa dipendenza sul classpath, ed è quasi sempre causata da una diversa superclass rispetto a quella originariamente compilata per essere sul classpath o qualche importazione della chiusura transitiva diversa, ma generalmente in class istanziazione e invocazione del costruttore. (Dopo aver eseguito correttamente il caricamento della class e l’invocazione del ctor, otterrai NoSuchMethodException o whatnot.)

Se il comportamento appare casuale, è probabile che il risultato di un programma multithreading possa classificare diverse dipendenze transitive in base al codice che è stato colpito per primo.

Per risolvere questi problemi, provare ad avviare la VM con -verbose come argomento, quindi osservare le classi che venivano caricate quando si verifica l’eccezione. Dovresti vedere alcune informazioni sorprendenti. Ad esempio, avere più copie della stessa dipendenza e delle stesse versioni che non ti saresti mai aspettato o che avresti accettato se tu sapessi che erano incluse.

Risolvendo i contenitori duplicati con Maven è meglio fare una combinazione del plugin maven-dependency e di maven-enforcer-plugin sotto Maven (o del grafico di dipendenza di SBT Plugin , quindi aggiungere quei vasi a una sezione del tuo POM di primo livello o come dipendenza importata elementi in SBT (per rimuovere quelle dipendenze).

In bocca al lupo!

Ho anche scoperto che, quando si utilizza JNI, invocando un metodo Java da C ++, se si passano i parametri al metodo Java invocato nell’ordine sbagliato, si otterrà questo errore quando si tenta di utilizzare i parametri all’interno del metodo chiamato (perché essi non sarà il tipo giusto). Inizialmente sono stato preso alla sprovvista dal fatto che JNI non esegue questo controllo per te come parte del controllo della firma della class quando invochi il metodo, ma presumo che non eseguano questo tipo di controllo perché potresti passare i parametri polimorfici e devono Supponi di sapere cosa stai facendo.

Esempio di codice 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. } 

Esempio di codice 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... } } } 

Ho avuto lo stesso problema, e in seguito ho capito che sto eseguendo l’applicazione su Java versione 1.4 mentre l’applicazione è compilata sulla versione 6.

In realtà, la ragione era di avere una libreria duplicata, una si trova all’interno del classpath e l’altra è inclusa in un file jar che si trova all’interno del classpath.

Un’altra situazione in cui questo errore può apparire è con Emma Code Coverage.

Questo accade quando si assegna un object a un’interfaccia. Immagino che questo abbia qualcosa a che fare con l’object che viene strumentato e non più compatibile con i binari.

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

Fortunatamente questo problema non si verifica con Cobertura, quindi ho aggiunto il plugin cobertura-maven nei miei plugin di segnalazione del mio pom.xml

Ho affrontato questo problema mentre stavo disimpegnando e ridistribuendo una guerra con glassfish. La mia struttura di class era così,

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

ed è stato cambiato in

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

Dopo aver fermato e riavviato il dominio, ha funzionato bene. Stavo usando glassfish 3.1.43

Ho un’applicazione web che distribuisce perfettamente bene sul tomcat della mia macchina locale (8.0.20). Tuttavia, quando l’ho messo nell’ambiente qa (tomcat – 8.0.20), continuava a darmi l’IncompatibleClassChangeError e mi lamentavo che stavo estendendo su un’interfaccia. Questa interfaccia è stata modificata in una class astratta. E ho compilato le classi genitore e figlio e continuavo a ottenere lo stesso problema. Infine, volevo eseguire il debug, quindi ho modificato la versione sul genitore su x.0.1-SNAPSHOT e poi ho compilato tutto e ora funziona. Se qualcuno sta ancora riscontrando il problema dopo aver seguito le risposte fornite qui, assicurati che anche le versioni nel tuo pom.xml siano corrette. Cambia le versioni per vedere se funziona. Se è così, quindi correggere il problema di versione.

La mia risposta, credo, sarà specifica per Intellij.

Mi ero rifatto pulito, arrivando persino a eliminare manualmente le directory “out” e “target”. Intellij ha un “invalidate cache e riavvio”, che a volte cancella gli errori dispari. Questa volta non ha funzionato. Le versioni delle dipendenze apparivano tutte corrette nel menu impostazioni progetto-> moduli.

La risposta finale è stata quella di eliminare manualmente la dipendenza del problema dal mio repository Maven locale. Una vecchia versione di bouncycastle era il colpevole (sapevo di aver appena cambiato le versioni e questo sarebbe stato il problema) e anche se la vecchia versione non mostrava dove fosse costruito, risolse il mio problema. Stavo usando intellij versione 14 e poi aggiornato a 15 durante questo processo.

Nel mio caso, mi sono imbattuto in questo errore in questo modo. pom.xml del mio progetto ha definito due dipendenze A e B E sia la dipendenza definita A che B dallo stesso artefatto (chiamiamola C ) ma diverse versioni di esso ( C.1 e C.2 ). Quando ciò accade, per ogni class in C ansible selezionare solo una versione della class dalle due versioni (mentre si costruisce un uber-jar ). Selezionerà la versione “più vicina” in base alle sue regole di mediazione delle dipendenze e visualizzerà un avviso “Abbiamo una class duplicata …” Se una firma metodo / class cambia tra le versioni, può causare un’eccezione java.lang.IncompatibleClassChangeError se la versione errata viene utilizzata in fase di esecuzione.

Avanzate: se A deve usare v1 di C e B deve usare v2 di C , quindi dobbiamo riposizionare C nei poms di A e B per evitare conflitti di class (abbiamo un avviso di class duplicato) quando si costruisce il progetto finale che dipende da entrambi A e B

Si prega di verificare se il proprio codice non è composto da due progetti di moduli che hanno gli stessi nomi di classi e definizione dei pacchetti. Ad esempio questo potrebbe accadere se qualcuno usa copia-incolla per creare una nuova implementazione di interfaccia basata sulla precedente implementazione.

Se questo è un record di possibili occorrenze di questo errore, allora:

Ho appena ricevuto questo errore su WAS (8.5.0.1), durante il caricamento di CXF (2.6.0) della configurazione spring (3.1.1_release) in cui una BeanInstantiationException ha lanciato un CXF ExtensionException, arrotolando un IncompatibleClassChangeError. Il seguente frammento mostra il senso della traccia dello stack:

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

In questo caso, la soluzione era di cambiare l’ordine del classpath del modulo nel mio file di guerra. Cioè, apri l’applicazione war nella console WAS sotto e seleziona i moduli client. Nella configurazione del modulo, imposta il caricamento della class come “genitore scorso”.

Questo si trova nella console WAS:

  • Applicatoins -> Tipi di applicazioni -> Applicazioni WebSphere Enterprise
  • Fai clic sul link che rappresenta la tua applicazione (guerra)
  • Fai clic su “Gestisci moduli” nella sezione “Moduli”
  • Fai clic sul link per i moduli sottostanti
  • Cambia “Ordine caricatore class” per essere “(genitore scorso)”.

Documentare un altro scenario dopo aver bruciato troppo tempo.

Assicurati di non avere un jar di dipendenza che abbia una class con un’annotazione EJB su di esso.

Avevamo un file jar comune con un’annotazione @local . Quella class è stata successivamente spostata da quel progetto comune e nel nostro progetto principale di jar ejb. Il nostro barattolo di ejb e il nostro barattolo comune sono entrambi impacchettati all’interno di un orecchio. La versione della nostra comune dipendenza jar non è stata aggiornata. Quindi 2 classi cercano di essere qualcosa con cambiamenti incompatibili.

Tutto quanto sopra – per qualsiasi ragione stavo facendo un grande refactoring e iniziando a ottenere questo. Ho ribattezzato il pacchetto in cui si trovava la mia interfaccia e l’ho risolto. Spero possa aiutare.

Per qualche motivo la stessa eccezione viene generata anche quando si usa JNI e si passa l’argomento jclass invece del jobject quando si chiama Call*Method() .

Questo è simile alla risposta di 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; } 

So che è un po 'tardi rispondere a questa domanda 5 anni dopo essere stato chiesto, ma questo è uno dei migliori successi nella ricerca di java.lang.IncompatibleClassChangeError quindi ho voluto documentare questo caso speciale.

Aggiungendo i miei 2 centesimi. Se stai usando scala e sbt e scala-logging come dipendenza, questo può succedere perché la versione precedente di scala-logging aveva il nome scala-logging-api.So, essenzialmente le risoluzioni delle dipendenze non avvengono a causa di differenti nomi che portano a errori di runtime durante l’avvio dell’applicazione scala.

Un’ulteriore causa di questo problema è la disponibilità Instant Run Studio per Android Studio.

La correzione

Se scopri di aver iniziato a ricevere questo errore, distriggers l’ Instant Run .

  1. Impostazioni principali di Android Studio
  2. Build, Execution, Deployment
  3. Esegui istantaneamente
  4. Deseleziona “Abilita esecuzione istantanea …”

Perché

Instant Run modifica un gran numero di cose durante lo sviluppo, per rendere più veloce la fornitura di aggiornamenti alla tua app in esecuzione. Quindi corsa istantanea. Quando funziona, è davvero utile. Tuttavia, quando si verifica un problema come questo, la cosa migliore da fare è distriggersre l’ Instant Run fino alla prossima versione di Android Studio.