Quelles sont les causes de java.lang.IncompatibleClassChangeError?

218

J'empaquette une bibliothèque Java en tant que JAR, et cela lance de nombreux java.lang.IncompatibleClassChangeErrors lorsque j'essaie d'en appeler des méthodes. Ces erreurs semblent apparaître au hasard. Quels types de problèmes peuvent être à l'origine de cette erreur?

Des morts-vivants
la source
Dans un projet Eclipse où je testais Apache FOP 1.0 et Barcode4J, les bibliothèques supplémentaires fournies avec Barcode4J remplaçaient apparemment celles fournies avec FOP (certaines avaient des numéros de version plus élevés). C'est un cas pour faire très attention à ce que vous mettez dans votre chemin de construction / chemin de classe.
Wivani

Réponses:

170

Cela signifie que vous avez apporté des modifications binaires incompatibles à la bibliothèque sans recompiler le code client. La spécification du langage Java §13 détaille toutes ces modifications, surtout, en changeant staticles champs / méthodes non-privés pour être staticou vice versa.

Recompilez le code client contre la nouvelle bibliothèque, et vous devriez être prêt à partir.

MISE À JOUR: Si vous publiez une bibliothèque publique, vous devez éviter autant que possible d'apporter des modifications binaires incompatibles afin de préserver ce que l'on appelle la «compatibilité descendante binaire». La mise à jour des pots de dépendance ne devrait idéalement pas, à elle seule, interrompre l'application ou la build. Si vous devez rompre la compatibilité descendante binaire, il est recommandé d'augmenter le numéro de version principal (par exemple de 1.xy à 2.0.0) avant de publier la modification.

notnoop
la source
2
Pour une raison quelconque, les développeurs ont ici un problème où la recompilation du code client ne résout pas le problème exactement. Pour une raison quelconque, s'ils modifient le fichier là où il se produit et recompilent l'erreur ne s'y produit plus, mais d'autres s'afficheront de manière aléatoire ailleurs dans le projet, où la bibliothèque est référencée. Je suis curieux de savoir ce qui pourrait se passer.
Zombies
5
Avez-vous essayé de faire un build propre (supprimer tous les *.classfichiers) et une recompilation? L'édition de fichiers a un effet similaire.
notnoop
Aucun code généré dynamiquement .... à moins que vous ne considériez un JSP comme tel. Nous avons essayé de supprimer les fichiers de classe et cela n'a pas semblé aider. La chose la plus étrange est que cela ne semble pas se produire pour moi, mais cela se produit pour d'autres développeurs.
Zombies
2
Pouvez-vous vous assurer que lorsque vous effectuez une génération propre, vous compilez avec les mêmes fichiers JAR avec lesquels vous exécutez?
notnoop
Y a-t-il quelque chose qui concerne la plate-forme 32 bits ou 64 bits, j'ai trouvé une erreur uniquement sur la plate-forme 64 bits.
cowboi-peng
96

Votre bibliothèque nouvellement empaquetée n'est pas binaire rétrocompatible (BC) avec l'ancienne version. Pour cette raison, certains des clients de bibliothèque qui ne sont pas recompilés peuvent lever l'exception.

Il s'agit d'une liste complète des modifications de l'API de la bibliothèque Java qui peuvent amener les clients créés avec une ancienne version de la bibliothèque à lancer java.lang. IncompatibleClassChangeError s'ils s'exécutent sur un nouveau (c'est-à-dire en cassant BC):

  1. Le champ non final devient statique,
  2. Le champ non constant devient non statique,
  3. La classe devient interface,
  4. L'interface devient classe,
  5. si vous ajoutez un nouveau champ à la classe / interface (ou ajoutez une nouvelle super-classe / super-interface), un champ statique d'une super-interface d'une classe client C peut masquer un champ ajouté (avec le même nom) hérité de la super-classe de C (cas très rare).

Remarque : Il existe de nombreuses autres exceptions causées par d'autres modifications incompatibles: NoSuchFieldError , NoSuchMethodError , IllegalAccessError , InstantiationError , VerifyError , NoClassDefFoundError et AbstractMethodError .

Le meilleur article sur BC est "Evolving Java-based APIs 2: Achieving API Binary Compatibility" écrit par Jim des Rivières.

Il existe également des outils automatiques pour détecter de tels changements:

Utilisation de japi-compliance-checker pour votre bibliothèque:

japi-compliance-checker OLD.jar NEW.jar

Utilisation de l'outil clirr:

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

Bonne chance!

linuxbuild
la source
59

Bien que ces réponses soient toutes correctes, la résolution du problème est souvent plus difficile. Il est généralement le résultat de deux versions légèrement différentes de la même dépendance à l'classpath, et est presque toujours causée soit par un autre superclasse que ce qui était à l' origine compilé contre être sur le chemin de classe ou une importation de la fermeture transitive étant différente, mais généralement à la classe instanciation et invocation de constructeur. (Après un chargement de classe et une invocation de ctor réussis, vous obtiendrez NoSuchMethodExceptionou autre chose.)

Si le comportement semble aléatoire, c'est probablement le résultat d'un programme multithread chargeant différentes dépendances transitives en fonction du code obtenu en premier.

Pour résoudre ces problèmes, essayez de lancer la machine virtuelle avec -verbosecomme argument, puis examinez les classes en cours de chargement lorsque l'exception se produit. Vous devriez voir des informations surprenantes. Par exemple, avoir plusieurs copies de la même dépendance et des versions auxquelles vous ne vous attendiez pas ou que vous auriez acceptées si vous saviez qu'elles étaient incluses.

La résolution des pots en double avec Maven est mieux fait avec une combinaison de maven-dépendance-plugin et plugin Maven-enforcer- sous Maven (ou de SBT dépendance graphique Plugin , puis en ajoutant les pots à une section de votre haut niveau POM ou la dépendance importés éléments dans SBT (pour supprimer ces dépendances).

Bonne chance!

Brian Topping
la source
5
L'argument bavard m'a aidé à identifier les pots qui posaient des problèmes. Merci
Virat Kadaru
6

J'ai également découvert que, lorsque vous utilisez JNI, en invoquant une méthode Java à partir de C ++, si vous passez des paramètres à la méthode Java invoquée dans le mauvais ordre, vous obtiendrez cette erreur lorsque vous tenterez d'utiliser les paramètres à l'intérieur de la méthode appelée (car ils ne sera pas le bon type). J'ai d'abord été surpris que JNI n'effectue pas cette vérification pour vous dans le cadre de la vérification de signature de classe lorsque vous appelez la méthode, mais je suppose qu'ils ne font pas ce type de vérification car vous pouvez passer des paramètres polymorphes et ils doivent supposez que vous savez ce que vous faites.

Exemple de code 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.
}

Exemple de code 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...
        }
    }
}
Psaume Ogre33
la source
1
Merci pour l'astuce. J'ai eu le même problème lors de l'appel d'une méthode Java avec la mauvaise instance (celle-ci) fournie.
sstn
5

J'ai eu le même problème, et plus tard j'ai compris que j'exécutais l'application sur Java version 1.4 tandis que l'application est compilée sur la version 6.

En fait, la raison était d'avoir une bibliothèque en double, l'une est située dans le chemin de classe et l'autre est incluse dans un fichier jar qui se trouve dans le chemin de classe.

Eng.Fouad
la source
Comment en êtes-vous arrivé au fond? Je suis sûr que j'ai un problème similaire, mais je ne sais pas comment tracer la bibliothèque de dépendances en cours de duplication.
beterthanlife
@beterthanlife écrit un script qui recherche dans tous les fichiers jar à la recherche de classes dupliquées (recherche par leur nom complet, c'est-à-dire avec le nom du package) :)
Eng.Fouad
ok, en utilisant NetBeans et maven-shadow je pense que je peux voir toutes les classes qui sont dupliquées et les fichiers jar dans lesquels elles résident. Beaucoup de ces fichiers jar que je n'ai pas référencés directement dans mon pom.xml, donc je suppose qu'ils doivent être inclus par mes dépendances. Existe-t-il un moyen facile de savoir quelle dépendance les inclut, sans passer par chacun des fichiers pom des dépendances? (veuillez pardonner ma noblesse, je suis vierge java!)
beterthanlife
@beterthanlife Mieux vaut poser une nouvelle question à ce sujet :)
Eng.Fouad
J'ai trouvé un pot en double en regardant le graphique des dépendances de gradle (./gradlew: <nom>: dépendances). J'ai donc trouvé le coupable qui a attiré l'ancienne version de la bibliothèque dans le projet.
nyx du
1

Une autre situation où cette erreur peut apparaître est avec la couverture de code Emma.

Cela se produit lors de l'affectation d'un objet à une interface. Je suppose que cela a quelque chose à voir avec l'objet étant instrumenté et non plus compatible binaire.

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

Heureusement, ce problème ne se produit pas avec Cobertura, j'ai donc ajouté cobertura-maven-plugin dans mes plugins de reporting de mon pom.xml

stivlo
la source
1

J'ai rencontré ce problème lors du retrait et du redéploiement d'une guerre avec le poisson-verre. Ma structure de classe était comme ça,

public interface A{
}

public class AImpl implements A{
}

et il a été changé en

public abstract class A{
}

public class AImpl extends A{
}

Après avoir arrêté et redémarré le domaine, cela a bien fonctionné. J'utilisais Glassfish 3.1.43

Nerrve
la source
1

J'ai une application Web qui se déploie parfaitement sur le tomcat de ma machine locale (8.0.20). Cependant, quand je l'ai mis dans l'environnement qa (tomcat - 8.0.20), il a continué à me donner l'IncompatibleClassChangeError et il se plaignait que je m'étendais sur une interface. Cette interface a été remplacée par une classe abstraite. Et j'ai compilé les classes parents et enfants et j'ai continué à obtenir le même problème. Enfin, je voulais déboguer, j'ai donc changé la version du parent en x.0.1-SNAPSHOT, puis j'ai tout compilé et maintenant ça fonctionne. Si quelqu'un rencontre toujours le problème après avoir suivi les réponses données ici, assurez-vous que les versions de votre pom.xml sont également correctes. Modifiez les versions pour voir si cela fonctionne. Si c'est le cas, corrigez le problème de version.

Jobin Thomas
la source
1

Je crois que ma réponse sera spécifique à Intellij.

J'avais reconstruit propre, allant même jusqu'à supprimer manuellement les répertoires "out" et "target". Intellij a un "invalider les caches et redémarrer", ce qui efface parfois les erreurs impaires. Cette fois, cela n'a pas fonctionné. Les versions des dépendances semblaient toutes correctes dans le menu des paramètres du projet-> modules.

La réponse finale a été de supprimer manuellement ma dépendance au problème de mon référentiel Maven local. Une ancienne version de bouncycastle était le coupable (je savais que je venais de changer de version et ce serait le problème) et même si l'ancienne version ne montrait aucun endroit dans ce qui était en cours de construction, elle a résolu mon problème. J'utilisais l'intellij version 14, puis mis à niveau vers 15 au cours de ce processus.

David Nickerson
la source
1

Dans mon cas, j'ai rencontré cette erreur de cette façon. pom.xmlde mon projet a défini deux dépendances Aet B. Et les deux Aet la Bdépendance définie sur le même artefact (appelez-le C) mais des versions différentes de celui-ci ( C.1et C.2). Lorsque cela se produit, pour chaque classe de Cmaven, vous ne pouvez sélectionner qu'une seule version de la classe parmi les deux versions (lors de la construction d'un uber-jar ). Il sélectionnera la version "la plus proche" en fonction de ses règles de médiation de dépendance et affichera un avertissement "Nous avons une classe en double ..." Si une signature de méthode / classe change entre les versions, cela peut provoquer une java.lang.IncompatibleClassChangeErrorexception si la version incorrecte est utilisé lors de l'exécution.

Avancé: si vous Adevez utiliser la v1 de Cet Bdevez utiliser la v2 de C, nous devons déplacer les poms Cdans Aet Bpour éviter les conflits de classe (nous avons un avertissement de classe en double) lors de la construction du projet final qui dépend à la fois de Aet B.

morpheus
la source
1

Dans mon cas, l'erreur est apparue lorsque j'ai ajouté la com.nimbusdsbibliothèque dans mon application déployée sur Websphere 8.5.
L'exception ci-dessous s'est produite:

Causée par: java.lang.IncompatibleClassChangeError: org.objectweb.asm.AnnotationVisitor

La solution était d'exclure le pot asm de la bibliothèque:

<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>
amine
la source
0

Veuillez vérifier si votre code ne se compose pas de deux projets de module qui ont les mêmes noms de classes et définitions de packages. Par exemple, cela peut se produire si quelqu'un utilise le copier-coller pour créer une nouvelle implémentation d'interface basée sur une implémentation précédente.

Denu
la source
0

S'il s'agit d'un enregistrement des occurrences possibles de cette erreur, alors:

Je viens de recevoir cette erreur sur WAS (8.5.0.1), lors du chargement CXF (2.6.0) de la configuration de printemps (3.1.1_release) où une BeanInstantiationException a généré une CXF ExtensionException, en remontant une IncompatibleClassChangeError. L'extrait suivant montre l'essentiel de la trace de la pile:

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

Dans ce cas, la solution a été de changer l'ordre des chemins de classe du module dans mon fichier war. Autrement dit, ouvrez l'application War dans la console WAS sous et sélectionnez le ou les modules client. Dans la configuration du module, définissez le chargement de classe sur "dernier parent".

Cela se trouve dans la console WAS:

  • Applicatoins -> Types d'application -> Applications WebSphere Enterprise
  • Cliquez sur le lien représentant votre candidature (guerre)
  • Cliquez sur "Gérer les modules" dans la section "Modules"
  • Cliquez sur le lien pour les modules sous-jacents
  • Remplacez «Ordre du chargeur de classes» par «(dernier parent)».
wmorrison365
la source
0

Documenter un autre scénario après avoir brûlé trop de temps.

Assurez-vous que vous n'avez pas de fichier de dépendance contenant une classe avec une annotation EJB.

Nous avions un fichier jar commun qui avait une @localannotation. Cette classe a ensuite été déplacée hors de ce projet commun et dans notre projet de pot ejb principal. Notre pot ejb et notre pot commun sont tous deux regroupés dans une oreille. La version de notre dépendance jar commune n'a pas été mise à jour. Ainsi 2 classes essaient d'être quelque chose avec des changements incompatibles.

Snekse
la source
0

Tout ce qui précède - pour une raison quelconque, je faisais un gros remaniement et commençais à l'obtenir. J'ai renommé le package dans lequel mon interface était et cela l'a effacé. J'espère que cela pourra aider.

bsautner
la source
0

Pour une raison quelconque, la même exception est également levée lors de l'utilisation de JNI et de la transmission de l'argument jclass au lieu du jobject lors de l'appel de a Call*Method().

Ceci est similaire à la réponse du Psaume Ogre33.

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

Je sais qu'il est un peu tard pour répondre à cette question 5 ans après avoir été posé, mais c'est l'un des meilleurs succès lors de la recherche, java.lang.IncompatibleClassChangeErrordonc je voulais documenter ce cas particulier.

Eric Obermühlner
la source
0

Ajouter mes 2 cents. Si vous utilisez scala et sbt et scala-logging comme dépendance; alors cela peut arriver parce que la version précédente de scala-logging avait le nom scala-logging-api.So; essentiellement, les résolutions de dépendance ne se produisent pas en raison de différences noms menant à des erreurs d'exécution lors du lancement de l'application scala.

sourabh
la source
0

Une autre cause de ce problème est si vous avez Instant Runactivé Android Studio.

Le correctif

Si vous constatez que vous commencez à obtenir cette erreur, désactivez-la Instant Run.

  1. Paramètres principaux d'Android Studio
  2. Construction, exécution, déploiement
  3. Course instantanée
  4. Décochez "Activer l'exécution instantanée ..."

Pourquoi

Instant Runmodifie un grand nombre de choses pendant le développement, pour accélérer la mise à jour de votre application en cours d'exécution. D'où une course instantanée. Quand ça marche, c'est vraiment utile. Cependant, lorsqu'un problème comme celui-ci se produit, la meilleure chose à faire est de désactiver Instant Runjusqu'à la sortie de la prochaine version d'Android Studio.

Knossos
la source