¿Qué causa java.lang.IncompatibleClassChangeError?

218

Estoy empaquetando una biblioteca Java como JAR, y arroja muchos correos java.lang.IncompatibleClassChangeErrorelectrónicos cuando trato de invocar métodos. Estos errores parecen aparecer al azar. ¿Qué tipo de problemas podrían estar causando este error?

Zombis
fuente
En un proyecto de Eclipse donde estaba probando Apache FOP 1.0 y Barcode4J, las bibliotecas adicionales que venían con Barcode4J aparentemente anulaban las que venían con FOP (algunas tenían números de versión más altos). Es un caso por tener mucho cuidado con lo que pones en tu ruta de compilación / classpath.
Wivani

Respuestas:

170

Esto significa que ha realizado algunos cambios binarios incompatibles en la biblioteca sin volver a compilar el código del cliente. La especificación del lenguaje Java §13 detalla todos estos cambios, lo más importante, cambiando staticlos campos / métodos no privados para que sean statico viceversa.

Vuelva a compilar el código del cliente con la nueva biblioteca, y debería estar listo.

ACTUALIZACIÓN: Si publica una biblioteca pública, debe evitar hacer cambios binarios incompatibles tanto como sea posible para preservar lo que se conoce como "compatibilidad binaria hacia atrás". La actualización de los archivos jar de dependencia por sí solo no debería romper la aplicación o la compilación. Si tiene que romper la compatibilidad con versiones anteriores binarias, se recomienda aumentar el número de versión principal (por ejemplo, de 1.xy a 2.0.0) antes de lanzar el cambio.

notnoop
fuente
2
Por alguna razón, los desarrolladores aquí tienen un problema en el que recompilar el código del cliente no soluciona el problema exactamente. Por alguna razón, si editan el archivo donde se produce y vuelven a compilar, el error ya no se produce allí, pero aparecerán más en cualquier lugar del proyecto, donde se hace referencia a la biblioteca. Tengo curiosidad por lo que podría estar causando esto.
Zombies
55
¿Has intentado hacer una compilación limpia (eliminar todos los *.classarchivos) y volver a compilar? La edición de archivos tiene un efecto similar.
notnoop
No hay código generado dinámicamente ... a menos que considere un JSP como tal. Intentamos eliminar los archivos de clase y esto no pareció ayudar. Lo extraño es que no parece que me suceda a mí, sino a otros desarrolladores.
Zombies
2
¿Puede asegurarse de que cuando realiza una compilación limpia compila contra los mismos frascos con los que ejecuta?
notnoop
¿Hay algo relacionado con la plataforma de 32 bits o 64 bits? Encontré un error que solo funciona en la plataforma de 64 bits.
cowboi-peng
96

Su biblioteca recién empaquetada no es compatible con binarios anteriores (BC) con la versión anterior. Por esta razón, algunos de los clientes de la biblioteca que no se vuelven a compilar pueden generar la excepción.

Esta es una lista completa de los cambios en la API de la biblioteca Java que pueden hacer que los clientes compilados con una versión anterior de la biblioteca arrojen java.lang. IncompatibleClassChangeError si se ejecutan en uno nuevo (es decir, romper BC):

  1. El campo no final se vuelve estático,
  2. El campo no constante se vuelve no estático,
  3. La clase se convierte en interfaz,
  4. La interfaz se convierte en clase,
  5. si agrega un nuevo campo a la clase / interfaz (o agrega una nueva superclase / superinterfaz), un campo estático de una superinterfaz de un cliente de clase C puede ocultar un campo agregado (con el mismo nombre) heredado del superclase de C (caso muy raro).

Nota : Hay muchas otras excepciones causadas por otros cambios incompatibles: NoSuchFieldError , NoSuchMethodError , IllegalAccessError , InstantiationError , VerifyError , NoClassDefFoundError y AbstractMethodError .

El mejor artículo sobre BC es "Evolución de las API 2 basadas en Java: lograr la compatibilidad binaria de API", escrito por Jim des Rivières.

También hay algunas herramientas automáticas para detectar tales cambios:

Uso de japi-compliance-checker para su biblioteca:

japi-compliance-checker OLD.jar NEW.jar

Uso de la herramienta clirr:

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

¡Buena suerte!

linuxbuild
fuente
59

Si bien todas estas respuestas son correctas, resolver el problema suele ser más difícil. Generalmente es el resultado de dos versiones ligeramente diferentes de la misma dependencia en el classpath, y casi siempre es causada por una superclase diferente de la que se compiló originalmente contra el classpath o por alguna importación del cierre transitivo diferente, pero generalmente en clase instanciación e invocación del constructor. (Después de la carga exitosa de la clase y la invocación del ctor, obtendrá NoSuchMethodExceptiono no).

Si el comportamiento parece aleatorio, es probable que el resultado de un programa multiproceso que cargue diferentes dependencias transitivas en función del código que se golpeó primero.

Para resolver esto, intente iniciar la VM con -verboseun argumento, luego observe las clases que se estaban cargando cuando se produce la excepción. Deberías ver información sorprendente. Por ejemplo, tener múltiples copias de la misma dependencia y versiones que nunca esperó o habría aceptado si supiera que se están incluyendo.

La resolución de frascos duplicados con Maven se realiza mejor con una combinación de maven-dependency-plugin y maven-enforcer-plugin en Maven (o el Dependency Graph Plugin de SBT , luego agrega esos frascos a una sección de tu POM de nivel superior o como dependencia importada elementos en SBT (para eliminar esas dependencias).

¡Buena suerte!

Brian Topping
fuente
55
El argumento detallado me ayudó a determinar qué jarras estaban dando problemas. Gracias
Virat Kadaru
6

También descubrí que, al usar JNI, invocando un método Java desde C ++, si pasa los parámetros al método Java invocado en el orden incorrecto, obtendrá este error cuando intente usar los parámetros dentro del método llamado (porque no será el tipo correcto) Inicialmente me sorprendió que JNI no haga esta verificación por usted como parte de la verificación de firma de clase cuando invoca el método, pero supongo que no hacen este tipo de verificación porque puede estar pasando parámetros polimórficos y tienen que Asume que sabes lo que estás haciendo.

Ejemplo de código 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.
}

Código Java de ejemplo:

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...
        }
    }
}
Salmo de ogro33
fuente
1
Gracias por la pista. Tuve el mismo problema al llamar a un método Java con la instancia incorrecta (esto) suministrada.
sstn
5

Tuve el mismo problema, y ​​luego descubrí que estoy ejecutando la aplicación en Java versión 1.4 mientras la aplicación se compila en la versión 6.

En realidad, la razón era tener una biblioteca duplicada, una se encuentra dentro del classpath y la otra se incluye dentro de un archivo jar que se encuentra dentro del classpath.

Eng.Fouad
fuente
¿Cómo llegaste al fondo de esto? Estoy seguro de que tengo un problema similar, pero no tengo idea de cómo rastrear qué biblioteca de dependencias se está duplicando.
beterthanlife
@beterthanlife escribe un script que busca dentro de todos los archivos jar buscando clases duplicadas (busca por su nombre completo, es decir, con el nombre del paquete) :)
Eng.Fouad
ok, usando NetBeans y maven-shade creo que puedo ver todas las clases que se están duplicando y los archivos jar en los que residen. Muchos de estos archivos jar no los he referenciado directamente en mi pom.xml, así que supongo que deben ser incluido por mis dependencias ¿Hay una manera fácil de averiguar qué dependencia los incluye, sin pasar por cada uno de los archivos pom de las dependencias? (¡perdón por mi novato, soy una virgen de Java!)
beterthanlife
@beterthanlife Mejor haga una nueva pregunta al respecto :)
Eng.Fouad
Encontré un jar duplicado mirando el gráfico de dependencias de gradle (./gradlew: <nombre>: dependencias). Así que encontré al culpable que atrajo la versión anterior de la biblioteca al proyecto.
nyx
1

Otra situación en la que puede aparecer este error es con Emma Code Coverage.

Esto sucede al asignar un objeto a una interfaz. Supongo que esto tiene algo que ver con el objeto que está siendo instrumentado y ya no es compatible con binarios.

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

Afortunadamente, este problema no ocurre con Cobertura, por lo que he agregado cobertura-maven-plugin en mis complementos de informes de mi pom.xml

stivlo
fuente
1

Me he enfrentado a este problema al desplegar y volver a desplegar una guerra con glassfish. Mi estructura de clase era así,

public interface A{
}

public class AImpl implements A{
}

y fue cambiado a

public abstract class A{
}

public class AImpl extends A{
}

Después de detener y reiniciar el dominio, funcionó bien. Estaba usando glassfish 3.1.43

Nerrve
fuente
1

Tengo una aplicación web que se implementa perfectamente en el tomcat de mi máquina local (8.0.20). Sin embargo, cuando lo puse en el entorno qa (tomcat - 8.0.20), me seguía dando el IncompatibleClassChangeError y se quejaba de que me estaba extendiendo en una interfaz. Esta interfaz se cambió a una clase abstracta. Y compilé las clases para padres e hijos y aún seguía teniendo el mismo problema. Finalmente, quería depurar, así que cambié la versión del padre a x.0.1-SNAPSHOT y luego compilé todo y ahora está funcionando. Si alguien sigue resolviendo el problema después de seguir las respuestas que se dan aquí, asegúrese de que las versiones en su pom.xml también sean correctas. Cambie las versiones para ver si eso funciona. Si es así, solucione el problema de la versión.

Jobin Thomas
fuente
1

Mi respuesta, creo, será específica de Intellij.

Había reconstruido limpio, incluso llegando a eliminar manualmente los directorios "out" y "target". Intellij tiene un "invalidar cachés y reiniciar", que a veces borra errores extraños. Esta vez no funcionó. Todas las versiones de dependencia parecían correctas en el menú de configuración del proyecto-> módulos.

La respuesta final fue eliminar manualmente la dependencia de mi problema de mi repositorio local de Maven. El culpable era una versión anterior de Bouncycastle (sabía que acababa de cambiar las versiones y ese sería el problema) y aunque la versión anterior no apareció en ningún lugar de lo que se estaba construyendo, resolvió mi problema. Estaba usando intellij versión 14 y luego actualicé a 15 durante este proceso.

David Nickerson
fuente
1

En mi caso, me encontré con este error de esta manera. pom.xmlde mi proyecto definió dos dependencias Ay B. Y ambas Ay la Bdependencia definida en el mismo artefacto (llámelo C) pero diferentes versiones del mismo ( C.1y C.2). Cuando esto sucede, para cada clase en Cmaven solo se puede seleccionar una versión de la clase de las dos versiones (mientras se crea un uber-jar ). Seleccionará la versión "más cercana" en función de sus reglas de mediación de dependencia y generará una advertencia "Tenemos una clase duplicada ..." Si una firma de método / clase cambia entre las versiones, puede causar una java.lang.IncompatibleClassChangeErrorexcepción si la versión incorrecta es utilizado en tiempo de ejecución.

Avanzado: si Adebe usar v1 de Cy Bdebe usar v2 de C, entonces debemos reubicarnos C en Ay Bpoms para evitar conflictos de clase (tenemos una advertencia de clase duplicada) al construir el proyecto final que depende de ambos Ay B.

Morfeo
fuente
1

En mi caso, el error apareció cuando agregué la com.nimbusdsbiblioteca en mi aplicación implementada Websphere 8.5.
Se produjo la siguiente excepción:

Causado por: java.lang.IncompatibleClassChangeError: org.objectweb.asm.AnnotationVisitor

La solución fue excluir el jar asm de la biblioteca:

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

Compruebe si su código no consta de dos proyectos de módulo que tengan los mismos nombres de clases y la misma definición de paquetes. Por ejemplo, esto podría suceder si alguien usa copiar y pegar para crear una nueva implementación de la interfaz basada en la implementación anterior.

denu
fuente
0

Si este es un registro de posibles ocurrencias de este error, entonces:

Acabo de recibir este error en WAS (8.5.0.1), durante la carga CXF (2.6.0) de la configuración de resorte (3.1.1_release) donde una excepción BeanInstantiationException acumula una excepción CXF ExtensionException, acumulando un IncompatibleClassChangeError. El siguiente fragmento muestra la esencia del seguimiento de la pila:

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

En este caso, la solución fue cambiar el orden de classpath del módulo en mi archivo war. Es decir, abra la aplicación war en la consola WAS y seleccione los módulos del cliente. En la configuración del módulo, establezca la carga de clase en "padre último".

Esto se encuentra en la consola WAS:

  • Applicatoins -> Tipos de aplicación -> Aplicaciones empresariales de WebSphere
  • Haga clic en el enlace que representa su aplicación (guerra)
  • Haga clic en "Administrar módulos" en la sección "Módulos"
  • Haga clic en el enlace para los módulos subyacentes
  • Cambie "Orden del cargador de clases" a "(padre último)".
wmorrison365
fuente
0

Documentar otro escenario después de quemar demasiado tiempo.

Asegúrese de no tener un jar de dependencia que tenga una clase con una anotación EJB.

Teníamos un archivo jar común que tenía una @localanotación. Esa clase luego se mudó de ese proyecto común a nuestro proyecto principal de ejb jar. Nuestra jarra ejb y nuestra jarra común están agrupadas dentro de una oreja. La versión de nuestra dependencia jar común no se actualizó. Así 2 clases intentan ser algo con cambios incompatibles.

Snekse
fuente
0

Todo lo anterior: por cualquier razón, estaba haciendo un gran refactor y comenzando a obtener esto. Cambié el nombre del paquete en el que estaba mi interfaz y eso lo borró. Espero que ayude.

bsautner
fuente
0

Por alguna razón, la misma excepción también se produce cuando se usa JNI y se pasa el argumento jclass en lugar del objeto de trabajo cuando se llama a Call*Method().

Esto es similar a la respuesta de 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;
}

Sé que es un poco tarde para responder esta pregunta 5 años después de haber sido preguntado, pero este es uno de los principales éxitos al buscar, java.lang.IncompatibleClassChangeErrorasí que quería documentar este caso especial.

Eric Obermühlner
fuente
0

Agregando mis 2 centavos. Si está usando scala y sbt y scala-logging como dependencia; entonces esto puede suceder porque la versión anterior de scala-logging tenía el nombre scala-logging-api. Entonces, esencialmente las resoluciones de dependencia no suceden debido a diferentes nombres que conducen a errores de tiempo de ejecución al iniciar la aplicación scala.

sourabh
fuente
0

Una causa adicional de este problema es si ha Instant Runhabilitado para Android Studio.

La solución

Si encuentra que comienza a recibir este error, desactívelo Instant Run.

  1. Configuración principal de Android Studio
  2. Compilación, Ejecución, Implementación
  3. Ejecución instantánea
  4. Desmarca "Habilitar ejecución instantánea ..."

Por qué

Instant Runmodifica una gran cantidad de cosas durante el desarrollo, para que sea más rápido proporcionar actualizaciones a su aplicación en ejecución. De ahí la ejecución instantánea. Cuando funciona, es realmente útil. Sin embargo, cuando surge un problema como este, lo mejor es apagar Instant Runhasta la próxima versión de Android Studio.

Knossos
fuente