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