– 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:
– Tá, mas e aí?
– Aí, na tela de Module properties, coloque o java.xml.bind de Available para 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
.
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.bind tá deprecated 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
.
– 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.
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.
Cada vez mais desanimado com o Java.
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: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 como1.8
, aí faziamsplit
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.
Como eu arrumo isso no JAVA EE?
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.
Eu utilizo o TomCat 8.5. Onde declaro essa linha?
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
.Alexandre, sou novo no JAVA EE, e não sei exatamente onde coloco essa linha.
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
Alexandre não sei se piora, mas uso OSX High Sierra.
No OS X, vai ser parecido com o Linux! Dê uma procurada sobre “variáveis de ambiente” no OS X.
Parabéns pelo Post. Gostei da forma que você se expressou.
Obrigado, Leandro!
Tentei escrever um diálogo descontraído!
No java 10 vai ter o mesmo problema?
Olá, Daniel.
Infelizmente, em todas as versões posteriores ao Java 9, o mesmo problema acontecerá.
PS: Desculpe pela demora.
No Java 10 terá o mesmo problema?