Deployment, Java

Sofrência com Java 9: cadê meu JAXB?

Cara, cadê meu JAXB?
– Hein?

Pô, cara, tava empolgadão aqui com o JDK 9. Atualizei e tal. Aí tentei subir uma aplicação no Tomcat e outra no Jetty e recebi isso na cara:

org.springframework.beans.factory.BeanCreationException: 
  Error creating bean with name 'entityManagerFactory' defined in ServletContext resource [/WEB-INF/spring-context.xml]: 
  Invocation of init method failed; 
    nested exception is java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1589)
	...
Caused by: 
  java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException
  at org.hibernate.boot.spi.XmlMappingBinderAccess.(XmlMappingBinderAccess.java:43)

– Tô ligado. O famoso NoClassDefFoundError de JAXBException no Java 9.

Não subiu por causa do JAXB? Ué, aquela biblioteca de serialização Java/XML? Mas eu nem uso nos meus projetos…
– Você não usa, mas alguma dependência pode usar. O Hibernate, no caso.

E o JAXB não vem nas libs padrão do JRE?
– Vem, inclusive no JRE 9.

Uai… Por que o erro, então?
– É que o JAXB tá nas libs padrão, mas não fica disponível… Aí, o Hibernate vai usar e: BAM!

Tomcat

Como resolvo no Tomcat?
– Seta a variável de ambiente JAVA_OPTS, que o Tomcat usa pra pegar argumentos da JVM. Para deixar o JAXB disponível, usamos o argumento --add-modules com o valor java.xml.bind. Assim, ó:

export JAVA_OPTS="--add-modules java.xml.bind"

E resolve mesmo?
– Se for só o JAXB que tiver faltando, resolve. Sei de um grande site brasileiro que simplesmente fez isso e tá rodando bonito em produção! Mas depende. Você pode ter outros problemas.

Jetty

Uia! E no Jetty?
– É bem parecido, mas o nome da variável de ambiente é JAVA_OPTIONS. O valor vai ser igual. Tipo assim:

export JAVA_OPTIONS="--add-modules java.xml.bind"

Maven

E se eu tiver rodando em desenvolvimento, com aquele plugin do Jetty pro Maven?
– Tipo mvn jetty:run?

Isso!
– Ah, aí você pode usar a variável de ambiente MAVEN_OPTS. Tipo:

export MAVEN_OPTS="--add-modules java.xml.bind"

Se você tiver usando o goal jetty:run-forked, pode também usar a opção jvmArgs no pom.xml.

Pro plugin do Tomcat deve ser parecido, né?
– Não vi, mas acho que sim…

Standalone

Tá. E é só no Tomcat ou Jetty que dá esse pau?
– Não. Se seu código usar o JAXB, vai dar pau. Mesmo um código simples, tipo esse:

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
//...
public class Teste {
  public static void main(String[] args) throws JAXBException {
    JAXBContext ctx= JAXBContext.newInstance(Cliente.class);
    //...
  }
}

Vamos dizer que você tinha esse código compilado com o JDK 8.

Se tentar rodar no JRE 9, com java Teste, você vai tomar na cara uma exceção parecida com a do Tomcat e do Jetty:

Error: Unable to initialize main class Teste
Caused by:
  java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException

E se essa classe tiver num JAR?
– Vai dar o mesmo erro quando você fizer o java -jar teste.jar.

Como resolve?
– Precisamos usar aquela tretinha também ao executar:

java --add-modules java.xml.bind Teste

Então com o JAR vai ser: java --add-modules java.xml.bind -jar teste.jar?
– Isso mesmo!

Opa! Aí sim! E se eu não usar o JAXB no meu código? Vai funcionar?
– Provavelmente sim. Mas tem outras coisas que podem dar um pau parecido. Então, não dá pra dizer…

Compilando

Mas e se eu tentar compilar com o JDK 9?
– Se usar o JAXB, vai dar pau logo de cara. Se você fizer javac Teste.java vai rolar erro de compilação já nos imports:

Teste.java:1: error: package javax.xml.bind is not visible
import javax.xml.bind.JAXBContext;
                ^
 (package javax.xml.bind is declared in module java.xml.bind, which is not in the module graph)
Teste.java:2: error: package javax.xml.bind is not visible
import javax.xml.bind.JAXBException;
                ^
 (package javax.xml.bind is declared in module java.xml.bind, which is not in the module graph)
2 errors

Para compilar com sucesso, tem que passar o tal --add-modules:

javac --add-modules java.xml.bind Teste.java

Aí compila tudo direitinho!

E se tiver usando o Maven pra compilar esse código?
– Se as propriedades maven.compiler.source e maven.compiler.target tiverem até 1.8, não tem problema.

Mesmo se eu só tiver o JDK 9 na máquina pra compilar?
– Sim. Porque o plugin de compilação do Maven não usa o javac, pelo menos do 3.0 pra frente.

Caramba! Mas e se eu pôr <maven.compiler.source>1.9<maven.compiler.source>?
– Aí, ferra… Vai dar aquele erro de compilação dizendo que o pacote javax.xml.bind não é visível.

E como arruma?
– Tem que configurar o plugin de compilação do Maven e passar o famoso --add-modules. Tipo assim:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <version>3.7.0</version>
  <configuration>
    <compilerArgs>
      <arg>--add-modules</arg>
      <arg>java.xml.bind</arg>
    </compilerArgs>
  </configuration>
</plugin>

Repare que não pode colocar num elemento <arg> só, porque não pode ter espaço no valor.

Vai entender…
– Pois é…

Eclipse

Dá pra rodar o Java 9 com o Eclipse?
– Hmmm… Até dá, se você baixar a última versão, a Oxygen 1a. Aliás, foi lançada hoje! Antes tinha que instalar um plugin.

Um detalhe interessante é que, lá no eclipse.ini, o parâmetro -vmargs tem o valor --add-modules=ALL-SYSTEM.

Meio parecido com o que a gente usou nas variáveis de ambiente mas o valor é diferente, né?
– Exato!

E aí posso criar um projeto Java 9 no Eclipse?
– Sim. É só escolher o execution environment JavaSE-9 ou apontar pra um JRE 9 específico para o projeto.

O código lá que usa o JAXBContext vai compilar de cara nesse projeto?
– O pior é que não…

Poxa…
Assim… Se não usasse diretamente o JAXB, compilaria de boa. Tipo as suas aplicações, que não dependem direto JAXB. É o Hibernate que depende, mas você não precisa compilar. Você pega os .class do JAR.

Sei…
Mas como esse código da classe Teste usa diretamente o JAXBContext, não dá não…

Precisamos ir no Build Path do projeto e, no Modulepath, editar a configuração Is modular do JRE 9 do seu projeto:

Modulepath > JavaSE-9 > Is module > Edit...

Tá, mas e aí?
– Aí, na tela de Module properties, coloque o java.xml.bind de Available para Explicitly included

Module properties > Available > Explicitly included

Aí compila?
– Opa! Sucesso! Só que ao rodar com o JRE 9, vai dar o pau de NoClassDefFoundError de JAXBException.

Eita!
– Pois é… Vamos ter que ir no Run Configurations > Java Application > Arguments > VM Arguments e pôr --add-modules java.xml.bind.

Run Configurations > Java Application > Arguments > VM arguments

Aí deve rodar de boa!

E se tiver rodando o Maven por dentro do Eclipse?
– Vai ser parecido. Dá erro ao rodar. Aí, tem que ir no Run Configurations > JRE > VM Arguments e colocar --add-modules java.xml.bind pra arrumar.

JAXB como dependência

Tem outra maneira de resolver sem usar essa treta de --add-modules?
– Sempre tem, né? Se não quiser usar esse argumento da JVM, dá pra colocar a API e uma implementação do JAXB como dependência do seu projeto.

Como assim?
– Tipo, botar no pom.xml isso daqui:

<dependency>
  <groupId>javax.xml.bind</groupId>
  <artifactId>jaxb-api</artifactId>
  <version>2.2.11</version>
</dependency>
<dependency>
  <groupId>com.sun.xml.bind</groupId>
  <artifactId>jaxb-core</artifactId>
  <version>2.2.11</version>
</dependency>
<dependency>
  <groupId>com.sun.xml.bind</groupId>
  <artifactId>jaxb-impl</artifactId>
  <version>2.2.11</version>
</dependency>

Ah, entendi…
– Na real, essa é a solução mais recomendada. A do --add-modules vai dar pau nas próximas versões.

Bibliotecas

Hmmm… E como as bibliotecas tem resolvido essas questões?
– Você diz para os caras compilarem no JDK 9 e, quem for usar, puder rodar no JRE 9?

Sei lá… Acho que é…
– Ah, as últimas versões do Hibernate e do Spring fizeram esse esquema de depender de uma implementação do JAXB e de outras bibliotecas que eles usam. Já o JOOQ, decidiu deixar que o usuário coloque --add-modules por enquanto, até que modularize o JOOQ pra valer. O Jetty também deixou pro usuário e, pra compilar, usaram --add-modules java.se.ee.

Tá meio diferente do nosso, o valor do --add-modules, né?
– É!

O porquê

Tá entendi como resolver pra uma série de ferramentas. Mas por que diabos acontece esse erro no Java 9 e não acontecia antes?
– Bom. Acho que você já ouviu falar que a grande novidade do Java 9 é o conceito de módulos e a modularização do próprio JDK.

Sim, sim.
– O JDK tem o módulo java.base que é o fundamento. Nesse módulo tão os pacotes java.lang, java.util, java.time, java.text, java.io

Aqueles que a gente sempre usa, né?
– Isso! Aí tem uns que a gente nem usa tanto, né? Tipo o módulo java.rmi, o java.desktop que tem os pacotes pra fazer telinhas Swing/AWT. Esses tão fora do java.base.

E o Java FX?
– Esse tá em uma série de outros módulos: javafx.base, javafx.controls, javafx.fxml, javafx.web e por aí vai… Também tão fora do base.

Saquei…
– Uma coisa curiosa é que não tão no base os dois módulos de JDBC, java.sql e java.sql.rowset, e nem o java.logging.

É mesmo?
– É. Aí, se você quiser criar um novo projeto com o Java 9 e quer que ele seja explicitamente modularizado, você tem definir uma pasta com o nome do módulo e dentro um arquivo module-info.java e os pacotes. Tipo assim:

projeto
  └── src
    └── br.com.empresa
        └── module-info.java
        └── br
             └── com
                 └── empresa
                     └── Teste.java

Detalhe que pasta dentro do src é br.com.empresa com os pontos mesmo. Já o pacote é uma pasta dentro da outra.

Bacana.
– Aí, esse seu módulo br.com.empresa vai depender apenas do módulo java.base. Se você quiser usar o JDBC, por exemplo, você tem que pôr dentro do module-info.java:

module br.com.empresa {
  requires java.sql;
}

Isso para um projeto novo, do Java 9 e modularizado.

Tá.
– Agora, imagina que pegamos um WAR ou JAR compilado com o JDK 8 e queremos rodar no JRE 9.

Java é retro-compatível, ué! Devia funcionar…
– Né? Pra boa parte dos projetos vai funcionar sim. Mas pra alguns casos vai dar pau…

É a vida…
– Uma coisa é certa: as classes compiladas com o JDK 8 não tem módulos. Mas o JRE 9 sempre precisa de módulos pra rodar.

Mas como ele faz então?
– O JRE 9 roda código sem módulos no “módulo sem nome”. Parece coisa de filme de terror, não?

É… hehe
– E esse “módulo sem nome” oferece para aplicação automaticamente o módulo java.se, que junta uma penca de coisa. A ideia é que tenha tudo o que uma aplicação Java SE antiga precisa pra rodar: além do java.base, tem o java.logging, o java.sql, o java.rmi, etc…

Entendi..
– Na real, você ainda pode programar em Java 9 sem módulos, do jeito antigo. É só não definir aquela pasta com o module-info.java no src e colocar direto os pacotes. Aí, você vai estar em um “módulo sem nome”.

E vai depender automaticamente do java.se
– Boa! Mas tem coisa que fica de fora desse java.se mas que tava disponível nas versões antigas do JRE.

Tipo o JAXB?
– Isso mesmo! O JAXB tá no módulo java.xml.bind mas não vem por padrão numa aplicação que está no “módulo sem nome”.

Ah….
– Daria pra modularizar o projeto e, no module-info.java colocar o requires java.xml.bind. Mas dá trabalho, né?

Dá trabalho sim.
– Então, o povo inventou esse argumento da VM pra oferecer módulos a mais pra aplicação: o --add-modules.

Agora entendi… Mas, vem cá, por que eles tiraram o JAXB, pô?!
– O JAXB e algumas outras APIs são coisa do Java EE e não do SE. Só que vinham embutidos no JRE. Algumas bibliotecas e aplicação usavam. Agora, vão remover. Se você olhar no Javadoc, o módulo java.xml.binddeprecated for removal.

Vão arrancar mesmo?
– Sim, mas só no futuro…

E quais APIs do Java EE tão nessa situação?
– Ah, tem a implementação do JAX-WS que vinha embutida no JRE, que tá nos módulos java.xml.ws e o java.xml.ws.annotation.

Aquela especificação pra WebServices SOAP vinha embutida no JRE? Não sabia…
– Pois é. Não é muito usado. Mas dá pra fazer fora de um servidor de aplicação isso daqui:

Endpoint.publish("http://localhost:9090/EstoqueWS", 
    new EstoqueWS());

Ó! Tem outros módulos do Java EE que vem no JRE?
– Tem também o java.corba, o java.activation e o java.transaction.

Todos esses serão removidos no futuro também?
– Sim.

Deve ter uma galerinha que usa o JAXB e o JAX-WS…
– O JAXB sim. O JAX-WS nem tanto, porque o povo usa a implementação do servidor de aplicação.

Poderiam ter deixado esse módulo java.xml.bind no java.se, né?
– Também acho!


– Aí, tem uma outra coisa: tem um módulo que junta isso tudo do Java EE que vai ser removido. É o módulo java.se.ee. E depende também do java.se.

Essa figura do Paul Bakker mostra bem quais são esses módulos do java.se.ee.

Módulos Java EE (Paul Bakker)

Daria pra usar --add-modules java.se.ee então?
– Sim, mas viria mais coisas que o JAXB.

É o que você tinha dito que o povo do Jetty fez pra compilar, né?
– Isso!

E no eclipse.ini tinha --add-modules ALL-SYSTEM. Que isso?
– É um módulo que inclui tudo mesmo, inclusive coisas específicas do JDK. Aí, vão vir aquelas classes que começam com com.sun. Ninguém devia depender dessas classes, mas vai ver o Eclipse precisa de algo, né?

Só pra lembrar, qual é a solução mais indicada mesmo?
– O melhor mesmo é depender de uma implementação do JAXB ou da outra lib que tiver faltando. Até segundo a turma da Oracle.

Anúncios

14 comentários sobre “Sofrência com Java 9: cadê meu JAXB?

  1. Poxa, apesar do seu post ser muito bom, eu fico frustado com tudo isso. Não é à toa que MUITOS aplicativos quebram miseravelmente ao rodar com JRE9.

  2. Ah, outra coisa que vi há umas 2 semanas:

    O Hibernate Validator 5.2.2.Final dava ArrayIndexOutOfBoundsException ao iniciar uma aplicação com JRE 9:

    
    Caused by: java.lang.ArrayIndexOutOfBoundsException: 1
        at org.hibernate.validator.internal.util.Version.getJavaRelease(Version.java:36)
        at org.hibernate.validator.internal.engine.ConfigurationImpl.(ConfigurationImpl.java:120)
        at org.hibernate.validator.internal.engine.ConfigurationImpl.(ConfigurationImpl.java:96)
        at org.hibernate.validator.HibernateValidator.createGenericConfiguration(HibernateValidator.java:31)
        at javax.validation.Validation$GenericBootstrapImpl.configure(Validation.java:276)
        ... 66 more
    

    O motivo é que a versão 5.2.2.Final do Hibernate Validator usava na implementação a propriedade java.specification.version pra detectar a versão do Java e esperava algo como 1.8, aí faziam split no ponto e pegavam a segunda posição (de índice 1).

    O problema é que, no Java 9, essa propriedade retorna apenas 9. Aí, como não tem segunda posição.
    Nas novas versões, o Hibernate Validator mudou a implementação.

    1. Tente apenas com o --add-modules java.xml.bind.

      Se não der, tente com o --add-modules java.se.ee.

      Se ainda não der, apele para o --add-modules ALL-SYSTEM, que é usado no Eclipse.

      Onde você vai colocar esse parâmetro depende do Servidor de Aplicação.

    2. Ah, com o Tomcat é mais fácil.
      É a primeira parte do artigo: basta usar a variável de ambiente JAVA_OPTS com o valor adequado. Provavelmente, vai ser --add-modules java.xml.bind.

  3. O JAVA_OPTS é uma variável de ambiente.

    No Linux, é usado um export.

    No Windows Windows 8 e 10, faça o seguinte :
    1) Em Pesquisar, procure e selecione: Sistema (Painel de Controle)
    2) Clique no link Configurações avançadas do sistema.
    3) Clique em Variáveis de Ambiente.
    5) Na seção Variáveis do Sistema, clique em Novo.
    6) Na janela Nova Variável de Sistema, especifique a variável de ambiente JAVA_OPTS. com o valor --add-modules java.xml.bind.
    7) Clique em OK. Feche todas as janelas restantes clicando em OK.
    8) Reinicie o Tomcat

      1. No OS X, vai ser parecido com o Linux! Dê uma procurada sobre “variáveis de ambiente” no OS X.

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.