O mínimo que você deve saber de Java 9

O título não é uma coincidência, esse post foi criado com o mesmo intuito daquele que eu escrevi em 2014 junto com o Paulo Silveira, anunciando as principais alterações da nova versão da linguagem Java. Com pequenas e enormes mudanças, o JDK 9 já está disponível para download.

São muitas as novidades e eu vou compartilhar aqui um resumo das principais delas, além de passar vários links e recursos que você pode utilizar para se atualizar.

Um java interativo

Ao baixar e configurar o Java 9 em seu ambiente você já consegue utilizar uma das grandes introduções da versão, que é a nova ferramenta REPL (Read-Eval-Print Loop).

Ela traz uma forma muito mais interessante de experimentar código, sem toda burocracia e verbosidade das classes com método main. Basta abrir o console, digitar jshell — que é o nome da ferramenta — e sair escrevendo código Java!

turini ~ $ jshell
| Welcome to JShell — Version 9
| For an introduction type: /help intro

Eu li a documentação completa da nova especificação da linguagem com o JShell aberto, experimentando cada uma das mudanças e com zero preocupações de setup. Foi uma experiência incrível.

Nesse outro post eu mostro vários exemplos com os principais detalhes e possibilidades.

E é muito legal também saber que você pode escrever algum snippet de código nele e depois compartilhar, pra que outras pessoas possam facilmente testar sua implementação — ou mesmo modificá-la e te enviar de volta.

Quer um exemplo? Podemos criar um método com o mesmo código usado no post de Java 8, para retornar a média de tamanho de uma lista de palavras.

Vou abrir o JShell e escrever o código em um método chamado averageLenght:

jshell> public double averageLenght(List<String> words) {
…> return words.stream()
…> .mapToInt(String::length).average()
…> .orElse(0);
…> }
	
| created method averageLenght(List<String>)

Agora criar uma lista de palavras:

jshell> List<String> words = List.of(“podemos”, “criar”, “listas”, “assim”);

E experimentar minha implementação:

jshell> averageLenght(words)
	
==> 5.2

Funciona!

Para salvar esse código e compartilhar, vou usar o comando /save.

jshell> /save average-lenght

O arquivo average-lenght foi criado. Experimente agora baixar esse arquivo e depois abrir o seu jshell com ele como a seguir:

turini ~ $ jshell average-lenght

A lista de palavras e o método averageLenght já vão estar aí, disponíveis para você testar.

Imagina que legal no fórum da Alura ou GUJ, por exemplo, você conseguir enviar o pedacinho de código problemático para que o pessoal abra e te ajude encontrar o problema? Você consegue criar classes, métodos e inclusive usar dependências externas — JARs de bibliotecas etc — nesse ambiente interativo, e tudo que a pessoa precisa é ter o Java 9 instalado.

Um Java reativo

O JDK 9 também evoluiu a arquitetura do pacote java.util.concurrent, com a introdução da Flow API que implementa a especificação de Reactive Streams. A API é composta pela classe abstrata Flow e suas interfaces internas, que seguem o padrão de publicador e assinante — publisher/subscriber pattern.

java.util.concurrent.Flow
java.util.concurrent.Flow.Publisher
java.util.concurrent.Flow.Subscriber
java.util.concurrent.Flow.Processor

Apesar do nome extremamente familiar, Reactive Streams não tem relação direta com a API de Streams do Java 8. Essa é uma confusão completamente comum, mas nesse caso o termo Streams remete a fluxo, fluxos reativos. Um passo criado a partir do caminho definido pelo famoso manifesto reativo.

Ele vem pra resolver o famoso problema de processamentos assíncronos em que um componente de origem envia dados sem saber ao certo se isso está em uma quantidade maior do que aquela com a qual o consumidor pode lidar.

Perceba que na imagem o componente de origem está recebendo e processando várias informações, mas muitas delas estão bloqueadas enquanto as outras estão sendo processadas. Os dados chegam em uma velocidade muito maior do que podem ser atendidos. Com a Flow API você consegue resolver esse e outros problemas avançados em execuções assíncronas com back pressure.

O fluxo seria assim:

No lugar de a origem empurrar uma quantidade arbitrária de dados para o destino, é o destino que puxa apenas a quantidade de dados que ele certamente poderá atender. Só então, os dados são enviados e na quantidade certa.

No exemplo a seguir estamos criando um publicador e registrando implicitamente um consumidor que apenas imprime as Strings conforme elas são enviadas:

jshell> SubmissionPublisher<String> publisher = new SubmissionPublisher<>();
	
jshell> publisher.consume(System.out::println);
	
jshell> List.of("esse", "código", "é", "assíncrono").forEach(publisher::submit);
	
esse
código
é
assíncrono

Um Java modular

Depois de mais de 20 anos muitos dos problemas da antiga estrutura monolítica da plataforma Java foram resolvidos com a introdução de um novo sistema de módulos e a modularização do próprio JDK. Neste outro post eu mostro o Jigsaw e o que muda na estrutura de seus projetos, além de mencionar brevemente como ficou a divisão de suas principais APIs.

No Java 9 tudo é modular, mas você não precisa sair migrando os seus projetos para usar essa nova versão. Especialmente por motivos de compatibilidade, projetos que não definem explicitamente o uso do sistema de módulos vão funcionar normalmente — internamente é declarado um unamed module, com acesso a todos os módulos da plataforma de forma parecida ao que acontece nas versões anteriores.

Apesar disso, depois de conhecer um pouco mais sobre os benefícios dessa migração eu arriscaria dizer que você vai querer migrar. Uma das grandes vantagens dessa nova estrutura é que, diferente da abordagem atual do classpath, as informações do módulo precisam ficar disponíveis da mesma forma durante as fases de compilação e execução. Isso garante uma integridade muito maior nos projetos, evitando problemas da abordagem atual do legado classpath — como o famoso JAR hell —  ou, pelo menos, reportando-os muito antes, em tempo de compilação.

Outro ganho enorme é no encapsulamento dos projetos, já que agora ser público não significa mais ser acessível. Em um sistema modular você precisa definir explicitamente o que pode ou não ser acessado por fora do módulo, e isso guia a construção de APIs com designs mais lógicos.

No exemplo a seguir o arquivo module-info.java, responsável pela definição de um módulo, exemplifica um caso em que o módulo br.com.caelum.desktop precisa do módulo base do Java FX para funcionar, e deixar acessível apenas seu pacote de componentes:

module br.com.caelum.desktop {
	requires javafx.base;
	exports br.com.caelum.desktop.components;
}	

Ah, e em projetos modulares você consegue criar imagens de execução customizadas com apenas o pedacinho da JRE que você está usando! Podemos falar que essa é uma extensão aos compact profiles do JDK 8, mas que funcionam de forma muito mais interessante já que você literalmente só carrega o que precisa. Muitos projetos pequenos podem ter só o java.base, no lugar de todos os 94 módulos e suas milhares de classes. Isso tem uma série de vantagens de performance e consumo de memória, já que a aplicação fica com um footprint inicial muito menor.

Novas APIs

A versão também recebeu diversas novas APIs. Um destaque especial vai para a de HTTP/2 Client, que possui uma interface publica completamente repaginada ante ao legado HttpURLConnection API e com suporte para requisições HTTP/2 e WebSockets.

Um exemplo de código para retornar o conteúdo do corpo de um request seria:

String contentBody = newHttpClient().send(
 newBuilder()
 .uri(new URI(“https://turini.github.io/livro-java-9/"))
 .GET()
 .build(), asString())
 .body();

O mais interessante dessa API é que ela é o piloto de um novo conceito da linguagem, que são os módulos em incubação. A ideia é permitir que APIs passem por um período de testes antes de entrar definitivamente para a linguagem, com objetivo de reduzir a possibilidade de introdução de erros na plataforma, que tem o forte peso da retrocompatibilidade.

Módulos em incubação ainda não fazem parte do Java SE. Eles ficam em um pacote jdk.incubator e não são resolvidos por padrão na compilação ou execução de suas aplicações. Para testar esse meu exemplo no jshell, por exemplo, você precisa iniciar a ferramenta com essa opção explicitamente:

turini ~ $ jshell --add-modules jdk.incubator.httpclient
| Welcome to JShell — Version 9
| For an introduction type: /help intro

O JDK 9 também traz uma nova API de logging, que te possibilita criar um provedor padrão de mensagens que poderá ser usado tanto em seu código como no do próprio JDK. E uma API de Stack-Walking, que tira proveito dos poderosos recursos da API de Streams para que você consiga passear pela Stack de sua aplicação de forma extremamente eficiente. Com ela podemos facilmente coletar em uma lista todas as classes de um pacote específico, ou alguma outra condição especial de filtro:

StackWalker.getInstance().walk(stream -> 
  stream.filter(frame -> frame.getClassName().contains("br.com.caelum")
))
.collect(toList());

Eu consigo ver um futuro próximo em que as IDEs e ferramentas de profilling tiram bastante proveito dessa API para fornecer alternativas performáticas e poderosas para analise e investigação de problemas de runtime.

Diversas mudanças nas APIs

Eu mostrei nesse post que as Collections receberam diversos factory methods trazendo uma forma mais próxima aos collection literals para criação de listas, sets e mapas.

Map.of(1,"Turini", 2,"Paulo", 3,"Guilherme");
	
List.of(1, 2, 3);
	
Set.of("SP", "BSB", "RJ");

O Stream também recebeu novos métodos como dropWhile, takeWhile, ofNullable e uma sobrecarga do iterate que permite definir uma condição para que a iteração termine. Repare a semelhança com o clássico for com index no exemplo em que imprime números de 1 a 10:

Stream
	.iterate(1, n -> n<=10, n -> n+1)
	.forEach(System.out::println);

Além das mudanças no Stream em si, diversas outras APIs receberam métodos que tiram proveito de seus recursos. Um exemplo seria o Optional, que agora consegue projetar seus valores diretamente para o Stream:

Stream<Integer> stream = Optional.of(ids).stream();

Essa transformação é extremamente útil quando você precisa aplicar diversas operações nos valores e tirar proveito da característica lazy da API.

O java.time também recebeu uma forma bastante interessante de retornar streams com intervalo de datas:

Stream<LocalDate> dates = 
	LocalDate.datesUntil(jdk10Release);

E o Scanner agora possui métodos como o findAll onde, dada uma expressão regular, retorna um stream de possíveis resultados.

String input = "esperei 3 anos pelo lançamento do java 9";

List<String> matches = new Scanner(input)
    .findAll("\\d+")
    .map(MatchResult::group)
    .collect(toList());

Extensão aos recursos da linguagem

Além de novos métodos e APIs, a linguagem recebeu um suporte maior na inferência de tipos e nos recursos existentes. O try-with-resources agora pode usar variáveis efetivamente finais, sem precisar re-instanciar como em sua versão agora antiga.

O código que era assim:

public void read(BufferedReader reader) {

  try (BufferedReader reader = reader) {
    //...
  } 
}

Agora pode ser escrito sem a necessidade de criar um novo tipo e da re-atribuição:

public void read(BufferedReader reader) {

  try (reader) {
    //...
  } 
}

O uso do operador diamante em classes anônimas e suporte aos métodos privados em interfaces — para reutilizar código dos default methods sem quebrar encapsulamento — são algumas das várias outras possibilidades. Muitas delas estão definidas na proposta Milling Project Coin, que recebeu esse nome por ser um aperfeiçoamento dos recursos introduzidos no Project Coin do JDK 7 — como o próprio operador diamante.

Performance

As melhorias relacionadas a performance também são incríveis. Um destaque especial foi a adoção do G1 como Garbage Collector padrão, como já era esperado. Ele é um algoritmo bem mais previsível, que executa seu trabalho em diferentes threads e em uma frequência muito maior, compactando o heap de memória durante a execução. O G1 também faz o reaproveitamento de Strings e tem diversos outros benefícios interessantes.

Por falar em Strings, elas agora possuem seu valor representado de uma forma muito mais eficiente em memória. Se você conferir na implementação das versões anteriores da linguagem, perceberá que uma String guarda seu valor interno em um array de caracteres, que mantém dois bytes para cada um deles.

private final char value[];

O problema da abordagem atual é que, na maior parte dos casos, as Strings usam valores em formato ISO-8859–1/Latin-1 que precisam de apenas um byte por caractere. Em outras palavras, poderíamos usar metade do espaço!

Bem, é o que estamos fazendo agora com Java 9! Experimente abrir o JShell e ver como esse campo de valor é declarado hoje:

jshell> String.class.getDeclaredField("value")
	
private final byte[] value;

Um array de bytes!

Com isso usamos apenas um byte, que será o suficiente na esmagadora maioria dos casos. Para os demais, foi adicionado um campo adicional de 1 byte para indicar qual o encoding que está sendo usado. Isso será feito automaticamente, baseado no conteúdo da String.

Essa proposta é conhecida como Compact Strings.

Ufa! Esse é resumo mínimo do que entrou na nova versão, mas acredite quando eu digo que tem muito mais além do que foi aqui mencionado. A própria Oracle amadureceu bastante suas documentações e trabalho na comunidade, com vários artigos e tutoriais bem interessantes. Vou deixar alguns links aqui pra quem quiser se aprofundar mais.

Onde estudar mais?

Entre os documentos oficiais dos arquitetos da plataforma, você certamente poderá se interessar em dar uma olhada nos release notes, com links inclusive para alguns tutoriais de migração, na lista com todas as especificações que entraram, além de alguns videos de experts introduzindo as mudanças.

No livro Java 9: Interativo, reativo e modularizado eu entro a fundo nas principais mudanças colocando tudo em um projeto prático, que no final é modularizado e você sente com as mãos na massa como é o processo de migração e os grandes benefícios da nova arquitetura modular da plataforma.

Também falei um pouco sobre as mudanças em um episódio da Alura Live, junto com o Gabriel — nosso host — e o Phil, que trabalha conosco no time de desenvolvimento da Alura.

Logo você também deve ver mais um monte de conteúdo aberto nosso sobre o assunto, entrando a fundo em detalhes específicos das muitas novidades da versão.

E ai, o que achou das mudanças?

23 Comentários

  1. Raphael lacerda 25/09/2017 at 14:10 #

    Essa api rest agora do JavaSE terá o mesmo nome do JavaEE ( jax-rs)?

    Ou vai ter um sombreamento?

  2. Rodrigo Turini 25/09/2017 at 15:12 #

    oi, Raphael

    O nome é HTTP/2 Client API, não é bem como o JAX-RS. É um cliente nativo de requests HTTP e WebSockets, como substituto para o HttpURLConnection API que era tão velho quanto o próprio http!

    Nessa API antiga o request parecia com esse:

    URL url = new URL("https://www.casadocodigo.com.br/"); 
    URLConnection urlConnection = url.openConnection();
    
    BufferedReader reader = new BufferedReader(
    	new InputStreamReader(urlConnection.getInputStream())); 
    
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
    reader.close(); 
    

    Tem mais infos aqui na documentação: http://openjdk.java.net/jeps/110

  3. Robson 25/09/2017 at 15:29 #

    Você comenta sobre ferramentas de profilling. Indicaria alguma boa ferramenta gratuita/open?

  4. Rodrigo Turini 25/09/2017 at 15:37 #

    oi, Robson

    O JProfiler já salvou minha vida algumas vezes (;

    https://www.ej-technologies.com/download/jprofiler/files

    (você pode usar junto com o JMeter, por exemplo, pra executar várias requests em pontos específicos da sua aplicação com o profilling rodando)

    Tem a VisualVM do próprio JDK também! É bem simples e ajuda muito

    https://docs.oracle.com/javase/8/docs/technotes/guides/visualvm/intro.html

  5. Weliff Lima 25/09/2017 at 23:29 #

    Overview excelente. Obrigado pelo post.

  6. Rafael Ponte 26/09/2017 at 11:00 #

    Excelente overview, Turini. Como sempre você tem dominado todos as nuances do Java e compartilhado de forma ímpar. Obrigado, meu amigo.

    No mais, do ponto de vista de desenvolvedor, eu ainda não consigo ver como tirar vantagens da nova modularização; se eu criar APIs (ou frameworks) eu consigo ver vantagens, mas no dia a dia eu ainda não consigo enxergar; se sou um vendor de servidor de aplicação também só vejo vantagens, afinal eles poderão isolar tudo de forma mais eficiente.

    Certamente estou miope em algum ponto. Você poderia me ajudar a enxergar onde poderíamos tirar vantagens dessa feature como um desenvolvedor enterprisey?

    No mais, parabéns novamente pelo post (por todos na verdade)!

  7. Marcos 26/09/2017 at 12:45 #

    O JDK 9 possui versão x86 ?

  8. Joel Lobo 26/09/2017 at 15:21 #

    module br.com.caelum.desktop {
    requires javafx.base;
    exposes br.com.caelum.desktop.components;
    }

    exposes seria exports?

  9. Rodrigo Turini 26/09/2017 at 15:34 #

    oi, Joel
    isso, é exports mesmo! Já ajustei no post. Muito obrigado pelo aviso

  10. Rodrigo Turini 26/09/2017 at 17:41 #

    Oi, Rafael

    É uma pergunta muito boa, e super complicada de responder! Vou tentar fazer um resumo dos pontos principais, mas logo quero escrever um post dedicado pra essa discussão e com exemplos práticos.

    Só o fato de transformar o seu projeto em um único módulo, mesmo não quebrando ainda em vários módulos para as diferentes features ou camadas, já tem algumas vantagens interessantes.

    A primeira é a possibilidade de usar o jlink pra criar uma runtime menor, no tamanho ótimo para o seu projeto — na alura, por exemplo, nós usamos só 5 dos 94 módulos do JRE/JDK (e o legal é que você nem precisa do java instalado aonde for usar essa imagem customizada, já que ela já tem todas as dependências que precisa pra executar seu projeto.. é um java portátil feito na medida para o seu projeto).

    Outro granho seria ter zero desses problemas de classpath de hoje em dia. Se existir dependência a mais ou a menos, você descobre em tempo de compilação e não runtime como acontece com os jar-hells da vida. No projeto modular existe uma integridade muito mais forte entre as fases de compilação e execução.

    Um terceiro ganho de migrar é que você percebe se está usando algum internal do java, que não deveria, e impede que os desenvolvedores usem por acidente de agora em diante. Hoje de manhã, por exemplo, enquanto eu migrava a alura encontrei um ponto do código que usava o org.omg.SendingContext.RunTime do CORBA. Se alguém quiser fazer isso de agora em diante vai precisar saber exatamente o que está fazendo, já que teria que declarar explicitamente a dependência do projeto com o module java.corba.

    E dar esse primeiro passo é relativamente simples, já que o que muda na estrutura do projeto é ter um diretório a mais e o arquivo module-info.java.

    Daí pra frente, quando seu projeto já for modular, abrem novos caminhos. No java de hoje, se algo é publico ele pode ser acessado. No projeto modular mesmo sendo publico ele fica visível apenas por dentro do próprio modulo (ou outras partes específicas que precisam ser selecionadas manualmente). Isso te obriga a pensar bem se você realmente quer criar certos tipos de acoplamento.

    Um exemplo seria na Alura, onde eu quero reescrever todo o código legado de integração com gateways de pagamento. No código de hoje eu tenho vários pacotes separados no projeto para o services, daos, dtos, controllers etc que fazem essa integracao. No projeto modular eu posso concentrar todos esses pacotes em um modulo, chamado br.com.alura.payments.paypal, por exemplo, para que apenas nesse ponto específico do projeto tenha o acoplamento com a dependência externa do paypal. Estando em um módulo separado eu também consigo garantir que todos os meus DTOs, modelos e implementações específicas dessa integração ficquem encapsulados e acessíveis por esse unico lugar. Os arquivos de configuração também ficam lá dentro.

    Faz sentido?

    Até agora existiam os pacotes, com um conjunto de classes. Os JARs, com um conjunto de pacotes e arquivos de configuracoes. O módulo entra como uma camada a mais ai no meio, que pode ter varios pacotes, jars, arquivos de configuracoes etc especificos e bem encapsulados. Diferente dos JARs, você escolhe o que expor, todo o resto fica extremamente encapsulado.

  11. Rodrigo Turini 26/09/2017 at 17:50 #

    Muito obrigado, Weliff!

    E Marcos, existe a versao para 32-bits sim. Na página de download, ao aceitar a licença deve aparecer uma opção para você.

    http://www.oracle.com/technetwork/java/javase/downloads/jdk9-downloads-3848520.html

  12. Rafael Ponte 27/09/2017 at 09:01 #

    Obrigado pela resposta, Turini. Faz sentido sim.

    Sobre encapsular sua classes e expor somente sua API foi fácil de entender, os alguns outros nem tanto. Agora, fiquei curioso sobre o que você fez com o projeto da Alura, você removeu uns 90% de módulos desnecessários e ainda não precisou instalar a JDK/JRE no servidor; poderia comentar um pouco mais sobre isso?

    Tem algum material/artigo bacana sobre jlink e como fazer isso? Acredito que isso melhora tanto a performance quanto escalabilidade da aplicação, afinal menos recursos serão utilizados ao rodar a aplicação. Ou estou pensando errado?

  13. Rodrigo Turini 27/09/2017 at 10:10 #

    Opa, é isso mesmo Rafael.

    E é extremamente simples!
    Olha como ficou o comando pra criar uma runtime só com os módulos que a Alura precisa:

    jlink --module-path $JAVA_HOME/jmods:mods \
    	--add-modules br.com.alura \
    	--output JRE-alura
    

    o jlink vai criar um diretório com o java lá dentro, e tudo que ele precisa pra funcionar. O java fica dentro de JRE-alura/bin/java.

    “Acredito que isso melhora tanto a performance quanto escalabilidade da aplicação, afinal menos recursos serão utilizados ao rodar a aplicação. Ou estou pensando errado?

    é exatamente isso!

    Aqui tem o link da proposta:
    http://openjdk.java.net/jeps/282

    E aqui um tutorial um pouco mais detalhado da Oracle:
    https://docs.oracle.com/javase/9/tools/jlink.htm

    [jaba] no livro eu mostro também como criar a runtime pra executar o projeto modular [/jaba]

    logo quero criar mais conteúdo aberto sobre o JPMS e a experiencia pratica de migrar os sistemas aqui da alura.

    ps: ela ainda não está rodando com JDK 9 em producão por causa de 2 dependências que ainda não dão suporte, mas já tem issue e só estamos esperando o release delas para dar o enter (;

  14. Sidney Amaral 27/09/2017 at 11:20 #

    Parabéns pelo post!

  15. Rafael Ponte 27/09/2017 at 13:49 #

    Poxa, bem legal. Mas fiquei na dúvida, no geral ele gera esse diretório com um JAR ou WAR? ou com as classes e JARs?

    Como rodar a aplicação num Tomcat da vida? Ou o Tomcat faria parte do artefato gerado pelo jlink?

  16. Luiz Jacó 27/09/2017 at 16:51 #

    Parabéns pelo post.

  17. Rodrigo Turini 27/09/2017 at 17:23 #

    opa, Rafael.
    O diretorío que ele gera é com o JRE (nao o seu JAR, ou WAR, ou nada interno do seu projeto).
    todo o resto continua igual!
    a úinca coisa que muda é que seu JAVA_HOME no servidor vai apontar para o diterório com esse JRE pequenininho, no lugar de apontar para o JRE normal com todos os 94 modulos.
    faz sentido?
    seu tomcat e o JAR, WAR — ou sela lá a forma que seu projeto seja empacotado — vao continuar os mesmos.

  18. Rafael Ponte 27/09/2017 at 18:40 #

    Agora entendi! Muito obrigado pela explicação e paciência!

    Para quem já teve que gerar instaladores de apps Java para Windows e Linux como eu, essa feature seria mão na roda na época!

  19. Marcos 03/10/2017 at 09:53 #

    Rodrigo aceitei a licença da oracle porém continua só aparece a x64.

  20. Jhonathan 04/10/2017 at 21:48 #

    Quando o alura vai lançar um curso sobre?

  21. Giuliano 05/10/2017 at 12:10 #

    Ótimo post, parabéns. Ainda estou tentando entender melhor para poder colocar na prática esses conceitos.
    Indo na linha de pensamento do Rafael, podemos criar um JRE baseado nos módulos que usamos no aplicação e fazemos o “tomcat” da vida apontar par esse JRE “lite”. Porém para o Tomcat funcionar ele precisa de certos módulos que eu não necessariamente coloquei como dependência nessa jre, nesse caso poderia gerar algum conflito? Ou ainda precisamos esperar para novas versões dos servidores compatíveis com o Java 9?

  22. Rodrigo Turini 05/10/2017 at 15:40 #

    oi, Giuliano

    não vai dar conflito, mas ao tentar executar ele vai mostrar uma mensagem avisando que os módulos x, y e z que são usados no projeto/servidor não estão presentes no JRE.

    Quando você cria uma runtime customizada e está faltando alguma dependencia, ele mostra um erro parecido com esse que eu dou de exemplo no livro:

    Error occurred during initialization of boot layer
    	java.lang.module.FindException: 
    Module jdk.incubator.httpclient not found, 
    	required by br.com.casadocodigo.http
    

    É bem expressivo o que falta e qual parte do projeto que precisa dele pra funcionar.

  23. Rodrigo Turini 05/10/2017 at 15:47 #

    Jhonathan, queremos ter cursos na alura em breve! (;

    Marcos, realmente não está mais aparecendo essa versão 32-bit.
    talvez esse post ajude
    https://stackoverflow.com/questions/46356345/can-java-9-run-on-a-32-bit-os

Deixe uma resposta