Cosa causa java.lang.IncompatibleClassChangeError?


218

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


In un progetto Eclipse in cui stavo testando Apache FOP 1.0 e Barcode4J, le librerie aggiuntive fornite con Barcode4J apparentemente avevano la precedenza su quelle fornite con FOP (alcune avevano numeri di versione più alti). È un caso per stare molto attenti a ciò che metti nel tuo percorso di costruzione / percorso di classe.
Wivani,

Risposte:


170

Ciò significa che hai apportato alcune modifiche binarie incompatibili alla libreria senza ricompilare il codice client. Java Language Specification §13 descrive in dettaglio tutte queste modifiche, soprattutto, cambiando staticcampi / metodi non non privati ​​in modo che siano statico viceversa.

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

AGGIORNAMENTO: se si pubblica una biblioteca pubblica, è necessario evitare il più possibile di apportare modifiche binarie incompatibili per preservare la cosiddetta "compatibilità binaria con le versioni precedenti". L'aggiornamento dei soli barattoli delle dipendenze idealmente non dovrebbe interrompere l'applicazione o la build. Se è necessario interrompere la compatibilità binaria con le versioni precedenti, si consiglia di aumentare il numero di versione principale (ad es. Da 1.xy a 2.0.0) prima di rilasciare la modifica.


2
Per qualche motivo gli sviluppatori qui hanno un problema in cui la ricompilazione del codice client non risolve esattamente il problema. Per qualche motivo se modificano il file nel punto in cui si verifica e ricompilano l'errore non si verifica più lì, ma più verranno visualizzati casualmente in altre parti del progetto, dove viene fatto riferimento alla libreria. Sono curioso di sapere cosa potrebbe esserlo.
Zombi,

5
Hai provato a fare una build pulita (eliminare tutti i *.classfile) e ricompilare? La modifica dei file ha un effetto simile.
notnoop,

Nessun codice generato dinamicamente .... a meno che non si consideri tale JSP. Abbiamo provato a eliminare i file di classe e questo non sembra essere d'aiuto. La cosa strana è che non sembra accadere per me, ma succede per altri sviluppatori.
Zombi,

2
Puoi assicurarti che quando esegui una build pulita stai compilando con gli stessi vasetti con cui corri?
notnoop,

C'è qualcosa che riguarda la piattaforma a 32 bit o 64 bit, ho trovato un errore che funziona solo nella piattaforma a 64 bit.
cowboi-peng,

96

La libreria appena impacchettata non è compatibile con le versioni precedenti binarie (BC) con la versione precedente. Per questo motivo alcuni client della libreria non ricompilati potrebbero generare l'eccezione.

Questo è un elenco completo delle modifiche nell'API della libreria Java che può causare il lancio 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 classe diventa interfaccia,
  4. L'interfaccia diventa classe,
  5. se aggiungi un nuovo campo alla classe / interfaccia (o aggiungi una nuova superclasse / superinterfaccia), un campo statico da una super interfaccia di un client di classe C potrebbe nascondere un campo aggiunto (con lo stesso nome) ereditato dal superclasse di C (caso molto raro).

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

L'articolo migliore su BC è "Evolving API based Java: Achieving API Binibility Compatibility" scritto da Jim des Rivières.

Esistono anche alcuni strumenti automatici per rilevare tali cambiamenti:

Utilizzo di japi-compliance-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!


59

Mentre queste risposte sono tutte corrette, spesso risolvere il problema è più difficile. Generalmente è il risultato di due versioni leggermente diverse della stessa dipendenza dal percorso di classe ed è quasi sempre causata da una superclasse diversa da quella originariamente compilata per essere diversa dal percorso di classe o dall'importanza di una diversa chiusura transitiva, ma generalmente a classe istanza e invocazione del costruttore. (Dopo il caricamento della classe e l'invocazione del ctor riusciti, otterrai NoSuchMethodExceptiono quant'altro.)

Se il comportamento appare casuale, è probabilmente il risultato di un programma multithread che carica diverse dipendenze transitive in base al codice che è stato colpito per primo.

Per risolverli, prova ad avviare la VM con -verbosecome argomento, quindi guarda le classi che venivano caricate quando si verifica l'eccezione. Dovresti vedere alcune informazioni sorprendenti. Ad esempio, avere più copie della stessa dipendenza e versioni che non ti saresti mai aspettato o che avresti accettato se sapessi che sarebbero state incluse.

La risoluzione di vasetti duplicati con Maven viene eseguita al meglio con una combinazione del plug- in Maven -Dipendenza e del plugin Maven -Enforcer in Maven (o Plugin del grafico delle dipendenze SBT , quindi aggiungendo tali vasetti a una sezione del POM di livello superiore o come dipendenza importata elementi in SBT (per rimuovere tali dipendenze).

In bocca al lupo!


5
L'argomentazione dettagliata mi ha aiutato a individuare quali barattoli stavano dando problemi. Grazie
Virat Kadaru,

6

Ho anche scoperto che, quando si utilizza JNI, invocando un metodo Java dal C ++, se si passano i parametri al metodo Java richiamato nell'ordine sbagliato, si otterrà questo errore quando si tenta di utilizzare i parametri all'interno del metodo chiamato (perché non sarà il tipo giusto). Inizialmente sono rimasto sorpreso dal fatto che JNI non effettui questo controllo per te come parte del controllo della firma di classe quando invochi il metodo, ma presumo che non eseguano questo tipo di controllo perché potresti passare parametri polimorfici e devono supponiamo che tu sappia 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...
        }
    }
}

1
Grazie per il suggerimento. Ho avuto lo stesso problema quando ho chiamato un metodo Java con l'istanza errata (questa) fornita.
1914

5

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

In realtà, il motivo 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.


Come sei arrivato in fondo a questo? Sono sicuro di avere un problema simile ma non ho idea di come rintracciare quale libreria di dipendenze viene duplicata.
beterthanlife

@beterthanlife scrive uno script che cerca all'interno di tutti i file jar alla ricerca di classi duplicate (cerca per il loro nome completo, cioè con il nome del pacchetto) :)
Eng.Fouad

ok, usando NetBeans e Maven-Shadow penso di poter vedere tutte le classi che vengono duplicate e i file jar in cui risiedono. Molti di questi file jar non ho fatto riferimento direttamente nel mio pom.xml, quindi presumo che debbano essere incluso dalle mie dipendenze. C'è un modo semplice per scoprire quale dipendenza li include, senza passare attraverso ciascuno dei file pom delle dipendenze? (per favore, perdona il mio noobishness, sono una vergine java!)
beterthanlife

@beterthanlife Meglio fare una nuova domanda a riguardo :)
Eng.Fouad

Ho trovato un vaso duplicato guardando il grafico delle dipendenze dei gradi (./gradlew: <nome>: dipendenze). Così ho trovato il colpevole che ha disegnato la vecchia versione della lib nel progetto.
Nyx,

1

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

Ciò accade quando si assegna un oggetto a un'interfaccia. Immagino che questo abbia qualcosa a che fare con l'oggetto 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 cobertura-maven-plugin nei miei plugin di segnalazione del mio pom.xml


1

Ho affrontato questo problema mentre non dispiegavo e ridistribuivo una guerra con il pesce di vetro. La mia struttura di classe era così,

public interface A{
}

public class AImpl implements A{
}

ed è stato modificato in

public abstract class A{
}

public class AImpl extends A{
}

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


1

Ho un'applicazione web che si implementa perfettamente sul mio tomcat della mia macchina locale (8.0.20). Tuttavia, quando l'ho inserito nell'ambiente qa (tomcat - 8.0.20), continuava a darmi IncompatibleClassChangeError e mi lamentavo che mi stavo estendendo su un'interfaccia. Questa interfaccia è stata cambiata in una classe 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 del genitore in x.0.1-SNAPSHOT e quindi ho compilato tutto e ora funziona. Se qualcuno continua a risolvere il problema dopo aver seguito le risposte fornite qui, assicurati che anche le versioni in pom.xml siano corrette. Cambia le versioni per vedere se funziona. In tal caso, quindi risolvere il problema di versione.


1

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

Avevo ricostruito pulito, arrivando persino a eliminare manualmente le directory "out" e "target". Intellij ha un "invalidare le cache e riavviare", che a volte cancella gli errori dispari. Questa volta non ha funzionato. Le versioni delle dipendenze sembravano tutte corrette nel menu Impostazioni-> Moduli del progetto.

La risposta finale è stata quella di eliminare manualmente la dipendenza dal mio problema dal repository Maven locale. Il colpevole era una vecchia versione di bouncycastle (sapevo di aver appena cambiato versione e che sarebbe stato questo il problema) e sebbene la vecchia versione non comparisse da nessuna parte in ciò che veniva costruito, risolveva il mio problema. Stavo usando intellij versione 14 e quindi aggiornato a 15 durante questo processo.


1

Nel mio caso, ho riscontrato questo errore in questo modo. pom.xmldel mio progetto ha definito due dipendenze Ae B. Ed entrambi Ae Bla dipendenza definita sullo stesso manufatto (lo chiamano C), ma diverse versioni di esso ( C.1e C.2). Quando ciò accade, per ogni classe in CMaven è possibile selezionare solo una versione della classe tra le due versioni (durante la creazione di un Uber-Jar ). Selezionerà la versione "più vicina" in base alle sue regole di mediazione di dipendenza e genererà un avviso "Abbiamo una classe duplicata ..." Se una firma metodo / classe cambia tra le versioni, può causare java.lang.IncompatibleClassChangeErrorun'eccezione se la versione errata è utilizzato in fase di esecuzione.

Avanzato: se è Anecessario utilizzare v1 di Ce Bdeve utilizzare v2 di C, è necessario riposizionare C in Ae B'poms per evitare conflitti di classe (abbiamo un avviso di classe duplicato) quando si costruisce il progetto finale che dipende da entrambi Ae B.


1

Nel mio caso l'errore è apparso quando ho aggiunto la com.nimbusdslibreria nella mia applicazione distribuita su Websphere 8.5.
Si è verificata la seguente eccezione:

Causato da: java.lang.IncompatibleClassChangeError: org.objectweb.asm.AnnotationVisitor

La soluzione era di escludere il barattolo asm dalla libreria:

<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

Verifica se il tuo codice non è composto da due progetti di moduli con gli stessi nomi di classi e definizione di pacchetti. Ad esempio, ciò potrebbe accadere se qualcuno utilizza copia-incolla per creare una nuova implementazione dell'interfaccia in base all'implementazione precedente.


0

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 CXF (2.6.0) della configurazione della molla (3.1.1_release) in cui una BeanInstantiationException ha eseguito il rollup di una CXF ExtensionException, eseguendo il roll-up di IncompatibleClassChangeError. Il frammento seguente mostra l'essenza 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.<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

In questo caso, la soluzione era cambiare l'ordine dei percorsi di classe del modulo nel mio file di guerra. Cioè, apri l'applicazione di guerra nella console WAS sotto e seleziona i moduli client. Nella configurazione del modulo, impostare il caricamento della classe su "padre ultimo".

Questo si trova nella console WAS:

  • Applicazioni -> Tipi di applicazione -> Applicazioni WebSphere Enterprise
  • Link clic che rappresenta la tua applicazione (guerra)
  • Fai clic su "Gestisci moduli" nella sezione "Moduli"
  • Fare clic sul collegamento per i moduli sottostanti
  • Modifica "Ordine del caricatore di classi" in "(ultimo padre)".

0

Documentare un altro scenario dopo aver bruciato troppo tempo.

Assicurati di non avere un vaso delle dipendenze con una classe con un'annotazione EJB su di essa.

Avevamo un file jar comune con @localun'annotazione. Quella classe è stata successivamente spostata da quel progetto comune e nel nostro principale progetto ejb jar. Il nostro vaso ejb e il nostro vaso comune sono entrambi raggruppati in un orecchio. La versione della nostra dipendenza jar comune non è stata aggiornata. Quindi 2 classi cercano di essere qualcosa con cambiamenti incompatibili.


0

Tutto quanto sopra - per qualsiasi motivo stavo facendo un grosso refattore e iniziando a ottenere questo. Ho rinominato il pacchetto in cui si trovava la mia interfaccia e questo è stato cancellato. Spero che aiuti.


0

Per qualche ragione la stessa eccezione viene generata anche quando si utilizza JNI e si passa l'argomento jclass invece del jobject quando si chiama a Call*Method().

Questo è simile alla risposta di Ogre Salmo33.

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 per rispondere a questa domanda 5 anni dopo essere stato chiesto, ma questo è uno dei migliori successi durante la ricerca, java.lang.IncompatibleClassChangeErrorquindi ho voluto documentare questo caso speciale.


0

Aggiungendo i miei 2 centesimi. Se stai usando scala e sbt e scala-logging come dipendenza; allora questo può accadere perché la versione precedente di scala-logging aveva il nome scala-logging-api.Quindi, essenzialmente le risoluzioni di dipendenza non si verificano a causa di diversi nomi che portano a errori di runtime durante l'avvio dell'applicazione scala.


0

Un'ulteriore causa di questo problema è se hai Instant Runabilitato Android Studio.

La correzione

Se scopri di iniziare a ricevere questo errore, disattiva Instant Run.

  1. Impostazioni principali di Android Studio
  2. Build, esecuzione, distribuzione
  3. Corsa istantanea
  4. Deseleziona "Abilita esecuzione immediata ..."

Perché

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.