O que causa o java.lang.IncompatibleClassChangeError?

218

Estou empacotando uma biblioteca Java como um JAR, e está lançando muitos java.lang.IncompatibleClassChangeErrors quando tento invocar métodos a partir dela. Esses erros parecem aparecer aleatoriamente. Que tipos de problemas podem estar causando esse erro?

Zumbis
fonte
Em um projeto Eclipse em que eu estava testando o Apache FOP 1.0 e o Barcode4J, as bibliotecas adicionais que acompanham o Barcode4J aparentemente estavam substituindo as que vieram com o FOP (algumas tinham números de versão mais altos). É um caso de ter muito cuidado com o que você coloca em seu caminho de construção / caminho de classe.
Wivani

Respostas:

170

Isso significa que você fez algumas alterações binárias incompatíveis na biblioteca sem recompilar o código do cliente. A Especificação da Linguagem Java §13 detalha todas essas alterações, principalmente, alterando staticcampos / métodos não privados para serem staticou vice-versa.

Recompile o código do cliente com a nova biblioteca e você deve estar pronto.

ATUALIZAÇÃO: Se você publicar uma biblioteca pública, evite fazer alterações binárias incompatíveis o máximo possível para preservar o que é conhecido como "compatibilidade com versões anteriores binárias". Atualizar os jars de dependência sozinho, idealmente, não deve interromper o aplicativo ou a compilação. Se você precisar interromper a compatibilidade binária com versões anteriores, é recomendável aumentar o número da versão principal (por exemplo, de 1.xy para 2.0.0) antes de liberar a alteração.

notnoop
fonte
2
Por alguma razão, os desenvolvedores aqui estão tendo um problema em que a recompilação do código do cliente não corrige o problema exatamente. Por alguma razão, se eles editarem o arquivo onde ocorre e recompilarem o erro, não ocorrerá mais lá, mas mais aparecerão aleatoriamente em outro local do projeto, onde a biblioteca é referenciada. Estou curioso para saber o que poderia estar ocasionando isso.
Zumbis
5
Você já tentou fazer uma compilação limpa (excluir todos os *.classarquivos) e recompilar? Arquivos de edição têm efeito semelhante.
notnoop 30/12/2009
Nenhum código gerado dinamicamente .... a menos que você considere um JSP como tal. Tentamos excluir os arquivos de turma e isso não pareceu ajudar. O estranho é que isso simplesmente não parece acontecer para mim, mas acontece para outro desenvolvedor.
Zombies
2
Você pode garantir que, ao fazer uma compilação limpa, esteja compilando nos mesmos frascos com os quais executa?
notnoop 30/12/2009
Existe algo preocupado com a plataforma de 32 bits ou 64 bits, eu encontrei um erro apenas executar na plataforma de 64 bits.
cowboi-Peng
96

Sua biblioteca recém-empacotada não é compatível com binários anteriores (BC) com a versão antiga. Por esse motivo, alguns dos clientes da biblioteca que não são recompilados podem lançar a exceção.

Este é um completo lista das alterações na API da biblioteca Java que podem fazer com que os clientes criados com uma versão antiga da biblioteca atinjam java.lang. IncompatibleClassChangeError se eles rodarem em um novo (por exemplo, quebrando o BC):

  1. O campo não final se torna estático,
  2. O campo não constante se torna não estático,
  3. A classe se torna interface,
  4. Interface se torna classe,
  5. se você adicionar um novo campo à classe / interface (ou adicionar nova superclasse / super interface), um campo estático de uma super interface de uma classe cliente C poderá ocultar um campo adicionado (com o mesmo nome) herdado do superclasse de C (caso muito raro).

Nota : Existem muitas outras exceções causadas por outras alterações incompatíveis: NoSuchFieldError , NoSuchMethodError , IllegalAccessError , InstantiationError , VerifyError , NoClassDefFoundError e AbstractMethodError .

O melhor artigo sobre BC é "Evoluindo APIs baseadas em Java 2: Atingindo a compatibilidade binária da API" escrito por Jim des Rivières.

Existem também algumas ferramentas automáticas para detectar essas alterações:

Uso do japi-compliance-checker na sua biblioteca:

japi-compliance-checker OLD.jar NEW.jar

Uso da ferramenta clirr:

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

Boa sorte!

linuxbuild
fonte
59

Embora todas essas respostas estejam corretas, resolver o problema geralmente é mais difícil. Geralmente, é o resultado de duas versões ligeiramente diferentes da mesma dependência no caminho de classe e quase sempre é causada por uma superclasse diferente do que foi originalmente compilado contra estar no caminho de classe ou por alguma importação do fechamento transitivo ser diferente, mas geralmente na classe instanciação e invocação de construtor. (Após o carregamento bem-sucedido da classe e a chamada do ctor, você receberáNoSuchMethodException ou não.)

Se o comportamento parecer aleatório, é provável que o resultado de um programa multithread carregue diferentes dependências transitivas com base em qual código foi atingido primeiro.

Para resolvê-los, tente iniciar a VM -verbosecomo argumento e observe as classes que estavam sendo carregadas quando a exceção ocorre. Você deve ver algumas informações surpreendentes. Por exemplo, ter várias cópias da mesma dependência e versões que você nunca esperava ou aceitaria se soubesse que elas estavam sendo incluídas.

A resolução de jarros duplicados com o Maven é melhor feita com uma combinação do maven-dependency-plugin e do maven-enforcer-plugin no Maven (ou no Dependency Graph Plugin do SBT , adicionando esses jars a uma seção do seu POM de nível superior ou como dependência importada elementos no SBT (para remover essas dependências).

Boa sorte!

Brian Topping
fonte
5
O argumento detalhado me ajudou a identificar quais frascos estavam dando problemas. Obrigado
Virat Kadaru
6

Também descobri que, ao usar JNI, invocando um método Java do C ++, se você passar parâmetros para o método Java invocado na ordem errada, você receberá esse erro ao tentar usar os parâmetros dentro do método chamado (porque eles não será do tipo certo). Inicialmente, fiquei surpreso que o JNI não faça essa verificação para você como parte da verificação de assinatura de classe quando você invoca o método, mas presumo que eles não fazem esse tipo de verificação porque você pode estar passando parâmetros polimórficos e eles precisam suponha que você saiba o que está fazendo.

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

Código Java de exemplo:

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 Ogro33
fonte
1
Obrigado pela dica. Eu tive o mesmo problema ao chamar um método Java com a instância errada (this) fornecida.
sstn
5

Eu tive o mesmo problema e, mais tarde, descobri que estou executando o aplicativo no Java versão 1.4 enquanto o aplicativo é compilado na versão 6.

Na verdade, o motivo foi ter uma biblioteca duplicada, uma localizada no caminho de classe e a outra incluída em um arquivo jar localizado no caminho de classe.

Eng.Fouad
fonte
Como você chegou ao fundo disso? Tenho certeza de que tenho um problema semelhante, mas não tenho idéia de como rastrear qual biblioteca de dependência está sendo duplicada.
Beterthanlife
@beterthanlife escrever um script que pesquisas dentro de todos os arquivos jar procurando aulas duplicados (pesquisa pelo seu nome completo qualificado, ou seja, com o nome do pacote) :)
Eng.Fouad
ok, usando o NetBeans e o maven-shade, acho que consigo ver todas as classes que estão sendo duplicadas e os arquivos jar em que residem. Muitos desses arquivos jar não referenciei diretamente no meu pom.xml, portanto, suponho que eles devem estar sendo incluído por minhas dependências. Existe uma maneira fácil de descobrir qual dependência está incluindo-os, sem passar pelo arquivo pom de cada uma das dependências? (por favor, perdoem minha noobishness, Im uma virgem java!)
beterthanlife
@beterthanlife melhor fazer uma nova pergunta sobre isso :)
Eng.Fouad
Encontrei um jar duplicado olhando para o gráfico de dependências gradle (./gradlew: <name>: dependencies). Então eu encontrei o culpado que chamou a versão antiga da lib para o projeto.
Nyx
1

Outra situação em que esse erro pode aparecer é com a Emma Code Coverage.

Isso acontece ao atribuir um objeto a uma interface. Eu acho que isso tem algo a ver com o objeto sendo instrumentado e não é mais compatível com binário.

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

Felizmente, esse problema não ocorre com o Cobertura, então eu adicionei o cobertura-maven-plugin nos meus plugins de relatórios do meu pom.xml

Stivlo
fonte
1

Eu enfrentei esse problema ao remover e reimplementar uma guerra com o glassfish. Minha estrutura de classe era assim,

public interface A{
}

public class AImpl implements A{
}

e foi alterado para

public abstract class A{
}

public class AImpl extends A{
}

Depois de parar e reiniciar o domínio, funcionou bem. Eu estava usando o glassfish 3.1.43

Nerrve
fonte
1

Eu tenho um aplicativo da Web que implanta perfeitamente no tomcat da minha máquina local (8.0.20). No entanto, quando o coloquei no ambiente qa (tomcat - 8.0.20), ele continuou fornecendo o IncompatibleClassChangeError e estava reclamando que eu estava estendendo uma interface. Essa interface foi alterada para uma classe abstrata. E eu compilei as classes pai e filho e continuava recebendo o mesmo problema. Finalmente, eu queria depurar, então mudei a versão no pai para x.0.1-SNAPSHOT e compilei tudo, e agora está funcionando. Se alguém ainda estiver enfrentando o problema após seguir as respostas fornecidas aqui, verifique se as versões do pom.xml também estão corretas. Mude as versões para ver se isso funciona. Nesse caso, corrija o problema da versão.

Jobin Thomas
fonte
1

Acredito que minha resposta seja específica da Intellij.

Eu havia reconstruído o clean, chegando a excluir manualmente os diretórios "out" e "target". O Intellij possui um "invalidar caches e reiniciar", que às vezes limpa erros estranhos. Desta vez não funcionou. Todas as versões de dependência pareciam corretas no menu Configurações do Projeto -> Módulos.

A resposta final foi excluir manualmente minha dependência do problema do meu repo maven local. Uma versão antiga do bouncycastle era a culpada (eu sabia que tinha acabado de alterar as versões e esse seria o problema) e, embora a versão antiga não aparecesse onde estava sendo construído, resolveu o meu problema. Eu estava usando a versão 14 do intellij e, em seguida, atualizei para 15 durante esse processo.

David Nickerson
fonte
1

No meu caso, encontrei esse erro dessa maneira. pom.xmldo meu projeto definiu duas dependências Ae B. E ambos Ae Bdependência definida no mesmo artefato (chame-o C), mas em versões diferentes ( C.1e C.2). Quando isso acontece, para cada classe no Cmaven, só é possível selecionar uma versão da classe entre as duas versões (durante a construção de um uber-jar ). Ele selecionará a versão "mais próxima" com base em suas regras de mediação de dependência e emitirá um aviso "Temos uma classe duplicada ..." Se uma assinatura de método / classe for alterada entre as versões, poderá causar uma java.lang.IncompatibleClassChangeErrorexceção se a versão incorreta for usado em tempo de execução.

Avançado: Se Adeve usar v1 de Ce Bdeve usar v2 de C, então devemos mudar C em Ae B's poms para conflito de classes evitar (temos uma advertência classe duplicado) ao construir o projeto final que depende tanto Ae B.

morfeu
fonte
1

No meu caso, o erro apareceu quando adicionei a com.nimbusdsbiblioteca no meu aplicativo implantado Websphere 8.5.
Ocorreu a exceção abaixo:

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

A solução foi excluir o jar asm da 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
fonte
0

Verifique se o seu código não consiste em dois projetos de módulo que possuem os mesmos nomes de classes e definição de pacotes. Por exemplo, isso poderia acontecer se alguém usasse copiar e colar para criar uma nova implementação de interface com base na implementação anterior.

denu
fonte
0

Se este é um registro de possíveis ocorrências desse erro, então:

Acabei de receber esse erro no WAS (8.5.0.1), durante o carregamento do CXF (2.6.0) da configuração de primavera (3.1.1_release) em que um BeanInstantiationException acumulou um CXF ExtensionException, acumulando um IncompatibleClassChangeError. O seguinte trecho mostra a essência do rastreamento de pilha:

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

Nesse caso, a solução foi alterar a ordem do caminho de classe do módulo no meu arquivo war. Ou seja, abra o aplicativo war no console do WAS e selecione o (s) módulo (s) do cliente. Na configuração do módulo, defina o carregamento da classe como "último pai".

Isso é encontrado no console do WAS:

  • Aplicativos -> Tipos de Aplicativos -> WebSphere Enterprise Applications
  • Clique no link que representa seu aplicativo (guerra)
  • Clique em "Gerenciar módulos" na seção "Módulos"
  • Clique no link para o (s) módulo (s) subjacente (s)
  • Altere "Ordem do carregador de classes" para "(último dos pais)".
wmorrison365
fonte
0

Documentar outro cenário depois de gastar muito tempo.

Verifique se você não possui um jar de dependência com uma classe com uma anotação EJB.

Tínhamos um arquivo jar comum que tinha uma @localanotação. Mais tarde, essa classe foi removida desse projeto comum para o nosso projeto principal do ejb jar. Nosso jarro ejb e nosso jarro comum estão ambos dentro do ouvido. A versão da nossa dependência de jar comum não foi atualizada. Assim, 2 classes tentando ser algo com alterações incompatíveis.

Snekse
fonte
0

Tudo isso - por qualquer motivo, eu estava fazendo um grande refator e começando a entender isso. Renomeei o pacote em que minha interface estava e isso foi liberado. Espero que ajude.

bsautner
fonte
0

Por alguma razão, a mesma exceção também é lançada ao usar JNI e transmitir o argumento jclass em vez do jobject ao chamar a Call*Method().

Isso é semelhante à resposta do 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;
}

Sei que é um pouco tarde para responder a essa pergunta cinco anos depois de ser perguntado, mas esse é um dos principais hits ao pesquisar, java.lang.IncompatibleClassChangeErrorentão eu queria documentar esse caso especial.

Eric Obermühlner
fonte
0

Se você estiver usando scala e sbt e scala-logging como dependência; isso pode acontecer porque a versão anterior do scala-logging tinha o nome scala-logging-api.So; essencialmente, as resoluções de dependência não acontecem devido a diferentes nomes que levam a erros de tempo de execução ao iniciar o aplicativo scala.

sourabh
fonte
0

Uma causa adicional desse problema é se você Instant Runativou o Android Studio.

O conserto

Se você começar a receber esse erro, desative Instant Run.

  1. Configurações principais do Android Studio
  2. Construção, Execução, Implantação
  3. Execução instantânea
  4. Desmarque "Ativar execução instantânea ..."

Por quê

Instant Runmodifica um grande número de coisas durante o desenvolvimento, para agilizar o fornecimento de atualizações para o aplicativo em execução. Daí a execução instantânea. Quando funciona, é realmente útil. No entanto, quando ocorre um problema como esse, a melhor coisa a fazer é desativar Instant Runaté a próxima versão do Android Studio.

Knossos
fonte