Java Módulos (JPMS)
Desde o Java 9, além de classes e pacotes, o Java tem um nível acima: o módulo. Um módulo é um grupo de pacotes com um "contrato" que diz de quais outros módulos ele precisa e quais dos seus pacotes os outros podem usar. O próprio JDK foi dividido em módulos, e é isso que permite ao jlink montar um Java enxuto, só com o que o seu programa usa.
- Que problema os módulos resolvem.
- Escrever um
module-info.javacomrequireseexports. - Compilar e rodar com
--module-pathe--module. - Entender os erros
is not visible. - Descobrir dependências com
jdeps. - Criar um Java sob medida com
jlink(e ver o tamanho). - Quando vale a pena usar módulos (spoiler: na maioria dos projetos, não).
JPMS = Java Platform Module System, "sistema de módulos da plataforma Java". Pré-requisitos: Pacotes e import, Modificadores de Acesso e a aula de JAR (classpath).
A ideia: o prédio com portaria
Pense num prédio comercial. Cada empresa (um pacote) tem as suas salas. Antes dos módulos, o Java era um prédio sem portaria: quem entrasse no classpath andava por todas as salas public de todas as empresas, inclusive as que eram só para uso interno. Os módulos acrescentam uma portaria em cada andar:
exports: "estas salas recebem visitas" (pacotes que outros módulos podem usar);requires: "este andar precisa visitar aquele outro" (de quais módulos eu dependo).
Tudo o que não é exportado fica escondido, mesmo que as classes sejam public. E as dependências ficam escritas: o Java confere, antes de rodar, se tudo de que o programa precisa está presente (em vez de quebrar no meio com um NoClassDefFoundError, como você viu na aula de JAR).
O JDK é feito de módulos
$ java --list-modules | wc -l 69 $ java --list-modules | head -12 java.base@25.0.4.1 java.compiler@25.0.4.1 java.datatransfer@25.0.4.1 java.desktop@25.0.4.1 java.instrument@25.0.4.1 java.logging@25.0.4.1 java.management@25.0.4.1 java.management.rmi@25.0.4.1 java.naming@25.0.4.1 java.net.http@25.0.4.1 java.prefs@25.0.4.1 java.rmi@25.0.4.1
São 69 módulos. O mais importante é o java.base, com o essencial (java.lang, java.util, java.io…). Outros são especializados: java.logging (logs), java.desktop (janelas), java.sql (bancos de dados). Para ver o que um módulo exporta:
$ java --describe-module java.base | head -5 java.base@25.0.4.1 exports java.io exports java.lang exports java.lang.annotation exports java.lang.classfile
1. Um projeto com dois módulos
A Ana reorganizou o caixa da aula de JAR em dois módulos:
br.com.ana.util: utilidades. Tem o pacotebr.com.ana.util(a classeMoeda, para todo mundo usar) e o pacotebr.com.ana.util.interno(a classeArredonda, um detalhe interno que ela não quer que ninguém use diretamente).br.com.ana.loja: o programa, com a classeCaixa.
A convenção é uma pasta por módulo, com o nome do módulo, e o module-info.java na raiz dela:
$ find src -type f | sort src/br.com.ana.loja/br/com/ana/loja/Caixa.java src/br.com.ana.loja/module-info.java src/br.com.ana.util/br/com/ana/util/interno/Arredonda.java src/br.com.ana.util/br/com/ana/util/Moeda.java src/br.com.ana.util/module-info.java
O módulo br.com.ana.util
module br.com.ana.util {
exports br.com.ana.util;
}
- module br.com.ana.util
- Declara um módulo e dá o nome dele. O costume é usar o nome do pacote principal (domínio ao contrário). O arquivo se chama sempre
module-info.java. - exports br.com.ana.util;
- "O pacote
br.com.ana.utilpode ser usado por outros módulos." O pacotebr.com.ana.util.internonão está na lista: fica escondido.
package br.com.ana.util;
import br.com.ana.util.interno.Arredonda;
public class Moeda {
public static String formatar(double valor) {
return String.format("R$ %.2f", Arredonda.centavos(valor));
}
}
package br.com.ana.util.interno;
public class Arredonda {
public static double centavos(double valor) {
return Math.round(valor * 100) / 100.0;
}
}
Dentro do mesmo módulo, a Moeda usa a Arredonda normalmente. O exports só vale para quem está fora.
O módulo br.com.ana.loja
module br.com.ana.loja {
requires br.com.ana.util;
requires java.logging;
}
- requires br.com.ana.util;
- "Este módulo usa o módulo
br.com.ana.util." Sem isso, nem os pacotes exportados dele ficam visíveis. - requires java.logging;
- O
Caixausajava.util.logging.Logger(veja Depurar), que fica no módulojava.loggingdo JDK. - (sem
requires java.base) - O
java.baseé exigido automaticamente por todo módulo.
package br.com.ana.loja;
import br.com.ana.util.Moeda;
import java.util.logging.Logger;
public class Caixa {
private static final Logger LOG = Logger.getLogger("caixa");
public static void main(String[] args) {
double total = 0;
for (String a : args) {
total += Double.parseDouble(a);
}
System.out.println("Itens: " + args.length);
System.out.println("Total: " + Moeda.formatar(total));
LOG.info("Venda registrada");
}
}
Repare: o código das classes é Java comum. Os módulos mexem só na "portaria"; o que acontece dentro das salas não muda.
Missão: o editor do guia não tem módulos, mas o código de dentro deles é o mesmo. Aqui as três classes estão num arquivo só (sem package). Complete o main do caixa: some os preços recebidos nos argumentos e mostre o total com Moeda.formatar, como na classe Caixa acima.
public class Main {
public static void main(String[] args) {
double total = 0;
// some os argumentos aqui
System.out.println("Itens: " + args.length);
// mostre "Total: " + o total formatado
}
}
class Moeda {
static String formatar(double valor) {
return String.format("R$ %.2f", Arredonda.centavos(valor));
}
}
class Arredonda {
static double centavos(double valor) {
return Math.round(valor * 100) / 100.0;
}
}
public class Main {
public static void main(String[] args) {
double total = 0;
for (String a : args) {
total += Double.parseDouble(a);
}
System.out.println("Itens: " + args.length);
System.out.println("Total: " + Moeda.formatar(total));
}
}
class Moeda {
static String formatar(double valor) {
return String.format("R$ %.2f", Arredonda.centavos(valor));
}
}
class Arredonda {
static double centavos(double valor) {
return Math.round(valor * 100) / 100.0;
}
}
2. Compilar com --module-path
O module path ("caminho dos módulos") é o equivalente do classpath para módulos: a lista de pastas ou JARs onde o Java procura módulos. Primeiro a Ana compila o util (que não depende de ninguém), depois o loja, apontando o module path para onde o util compilado ficou:
$ javac -d out/br.com.ana.util $(find src/br.com.ana.util -name "*.java") $ javac -p out -d out/br.com.ana.loja $(find src/br.com.ana.loja -name "*.java") $ find out -type f | sort out/br.com.ana.loja/br/com/ana/loja/Caixa.class out/br.com.ana.loja/module-info.class out/br.com.ana.util/br/com/ana/util/interno/Arredonda.class out/br.com.ana.util/br/com/ana/util/Moeda.class out/br.com.ana.util/module-info.class
- $(find src/br.com.ana.util -name "*.java")
- Um truque do Bash: o
findlista todos os.javado módulo, e a lista vira os argumentos dojavac. Inclui omodule-info.java. - -d out/br.com.ana.util
- Os
.classde cada módulo vão para uma pasta com o nome do módulo. - -p out
- Abreviação de
--module-path out: "procure módulos já compilados na pastaout". É assim que ojavacacha obr.com.ana.util.
O module-info.java também é compilado: vira module-info.class. Também dá para compilar todos os módulos de uma vez, com javac -d out --module-source-path src --module br.com.ana.loja,br.com.ana.util.
Rodar com --module
$ java -p out -m br.com.ana.loja/br.com.ana.loja.Caixa 4.50 10 2.25 Itens: 3 Total: R$ 16,75 out. 16, 2026 6:57:14 PM br.com.ana.loja.Caixa main INFORMAÇÕES: Venda registrada
- -p out
--module-path: onde estão os módulos.- -m br.com.ana.loja/br.com.ana.loja.Caixa
--module: o módulo inicial, uma barra, e a classe com omain.- 4.50 10 2.25
- Os argumentos do programa, como sempre.
JARs modulares
Um JAR com um module-info.class na raiz é um JAR modular. Com --main-class, a classe principal fica anotada no módulo, e basta dar o nome do módulo:
$ mkdir mods $ jar --create --file mods/util.jar -C out/br.com.ana.util . $ jar --create --file mods/loja.jar --main-class br.com.ana.loja.Caixa -C out/br.com.ana.loja . $ java -p mods -m br.com.ana.loja 2.50 2.50 Itens: 2 Total: R$ 5,00 out. 16, 2026 6:58:04 PM br.com.ana.loja.Caixa main INFORMAÇÕES: Venda registrada $ jar --describe-module --file mods/loja.jar br.com.ana.loja jar:file:///home/ana/modulos/mods/loja.jar!/module-info.class requires br.com.ana.util requires java.base mandated requires java.logging contains br.com.ana.loja main-class br.com.ana.loja.Caixa
(mandated = "obrigatório": o java.base que todo módulo recebe.) E um detalhe que facilita a vida: um JAR modular também funciona no classpath, como um JAR comum; aí o module-info é simplesmente ignorado:
$ java -cp mods/loja.jar:mods/util.jar br.com.ana.loja.Caixa 3 Itens: 1 Total: R$ 3,00 out. 16, 2026 6:58:04 PM br.com.ana.loja.Caixa main INFORMAÇÕES: Venda registrada
Tudo o que roda pelo classpath fica num módulo "sem nome" (o unnamed module), que enxerga tudo. É assim que todos os programas das aulas anteriores rodaram, sem que você precisasse saber de módulos.
3. A portaria funcionando: os erros is not visible
Usar um pacote que não foi exportado
A Ana tentou usar a Arredonda direto no Caixa (acrescentou import br.com.ana.util.interno.Arredonda;):
$ javac -p out -d out/br.com.ana.loja $(find src/br.com.ana.loja -name "*.java")
src/br.com.ana.loja/br/com/ana/loja/Caixa.java:4: error: package br.com.ana.util.interno is not visible
import br.com.ana.util.interno.Arredonda;
^
(package br.com.ana.util.interno is declared in module br.com.ana.util, which does not export it)
1 error
A classe Arredonda é public, mas o pacote dela não foi exportado: is not visible ("não é visível"), e a linha de baixo explica: declared in module br.com.ana.util, which does not export it. Isso é o que os módulos acrescentam ao public: público dentro do módulo não é o mesmo que público para o mundo.
Esquecer o requires
Com o module-info.java do loja vazio (module br.com.ana.loja { }, sem nenhum requires):
$ javac -p out -d out/br.com.ana.loja $(find src/br.com.ana.loja -name "*.java")
src/br.com.ana.loja/br/com/ana/loja/Caixa.java:3: error: package br.com.ana.util is not visible
import br.com.ana.util.Moeda;
^
(package br.com.ana.util is declared in module br.com.ana.util, but module br.com.ana.loja does not read it)
src/br.com.ana.loja/br/com/ana/loja/Caixa.java:4: error: package java.util.logging is not visible
import java.util.logging.Logger;
^
(package java.util.logging is declared in module java.logging, but module br.com.ana.loja does not read it)
2 errors
does not read it = "o módulo br.com.ana.loja não lê (não declarou que usa) aquele módulo". São dois: o br.com.ana.util, da Ana, e o java.logging, do próprio JDK. Conserto: um requires para cada.
Esquecer só o módulo do JDK
Com requires br.com.ana.util;, mas sem o requires java.logging;:
$ javac -p out -d out/br.com.ana.loja $(find src/br.com.ana.loja -name "*.java")
src/br.com.ana.loja/br/com/ana/loja/Caixa.java:4: error: package java.util.logging is not visible
import java.util.logging.Logger;
^
(package java.util.logging is declared in module java.logging, but module br.com.ana.loja does not read it)
1 error
Mesmo erro, agora com um módulo do próprio JDK. Programas no classpath enxergam todos os módulos do JDK; um módulo nomeado só enxerga o java.base e o que declarar. A mensagem já diz qual requires falta: declared in module java.logging.
4. jdeps: de que o código depende?
O jdeps (Java dependencies) analisa classes ou JARs compilados e lista de quais módulos eles dependem:
$ jdeps -s --module-path out out/br.com.ana.loja br.com.ana.loja -> br.com.ana.util br.com.ana.loja -> java.base br.com.ana.loja -> java.logging $ jdeps -s --module-path out out/br.com.ana.util br.com.ana.util -> java.base
(-s = summary, resumo.) Ele também funciona com JARs comuns, sem módulos, como os da aula de JAR. É útil para saber quais módulos do JDK um programa antigo usa, antes de usar o jlink:
$ jdeps --print-module-deps ~/jar-loja/caixa.jar ~/jar-loja/util.jar java.base $ jdeps -summary ~/jar-loja/caixa.jar caixa.jar -> java.base caixa.jar -> not found
O not found aparece porque, analisando o caixa.jar sozinho, o jdeps não achou a Moeda (ela está no util.jar). Passando os dois JARs, a resposta é: só o java.base.
5. jlink: um Java sob medida
Para rodar o caixa, a pessoa precisa de um Java instalado. O JDK inteiro do laboratório ocupa 303 MB, com 69 módulos, compilador, ferramentas… mas o caixa usa só 2 módulos do JDK. O jlink (Java linker, "montador") cria uma pasta com um Java só com os módulos necessários, junto com os seus:
$ jlink --module-path out --add-modules br.com.ana.loja --output caixa-java --strip-debug --no-header-files --no-man-pages --launcher caixa=br.com.ana.loja/br.com.ana.loja.Caixa $ du -sh caixa-java /opt/jdk-25 56M caixa-java 303M /opt/jdk-25 $ ls caixa-java caixa-java/bin caixa-java: bin conf legal lib release caixa-java/bin: caixa java keytool $ caixa-java/bin/java --list-modules br.com.ana.loja br.com.ana.util java.base@25.0.4.1 java.logging@25.0.4.1
- --module-path out
- Onde estão os seus módulos (os do JDK o
jlinkjá conhece). - --add-modules br.com.ana.loja
- O módulo raiz. O
jlinksegue osrequirese inclui sozinho obr.com.ana.util, ojava.logginge ojava.base. Por isso o programa precisa ser modular: ojlinkusa osmodule-infopara saber o que levar. - --output caixa-java
- A pasta que será criada.
- --strip-debug, --no-header-files, --no-man-pages
- Deixam de fora informações de depuração, arquivos para programar em C e manuais: tudo o que não faz falta para rodar.
- --launcher caixa=módulo/classe
- Cria um script
bin/caixaque já chama o programa.
De 303 MB para 56 MB, com 4 módulos em vez de 69. Essa pasta pode ser copiada (ou compactada) para outro computador Linux da mesma arquitetura e roda sem Java instalado. Ela é a base do jpackage, citado na aula de JAR. O lançador:
$ cat caixa-java/bin/caixa #!/bin/sh JLINK_VM_OPTIONS= DIR=`dirname $0` $DIR/java $JLINK_VM_OPTIONS -m br.com.ana.loja/br.com.ana.loja.Caixa "$@" $ caixa-java/bin/caixa 4.50 10 2.25 Itens: 3 Total: R$ 16.75 Oct 16, 2026 6:57:33 PM br.com.ana.loja.Caixa main INFORMAÇÕES: Venda registrada
Funcionou, mas olhe com atenção: R$ 16.75 com ponto, e a data em inglês (Oct). Com o JDK completo, era 16,75 e out.. O que sumiu? Os dados de idioma e formato de cada país ficam num módulo separado, o jdk.localedata, que nenhum requires pede, então o jlink não o incluiu. Sem ele, o Java só sabe formatar no padrão inglês. (O INFORMAÇÕES continua em português porque a tradução das mensagens de log mora no próprio java.logging.) Conserto: incluir o módulo à mão, só com os idiomas português (pt-*):
$ jlink --module-path out --add-modules br.com.ana.loja,jdk.localedata --include-locales='pt-*' --output caixa-java-br --strip-debug --no-header-files --no-man-pages --launcher caixa=br.com.ana.loja/br.com.ana.loja.Caixa $ du -sh caixa-java-br 56M caixa-java-br $ caixa-java-br/bin/caixa 4.50 10 2.25 Itens: 3 Total: R$ 16,75 out. 16, 2026 6:57:49 PM br.com.ana.loja.Caixa main INFORMAÇÕES: Venda registrada
A lição vale para qualquer jlink: o Java sob medida tem só o que foi pedido. Teste o programa com ele antes de entregar.
Missão: reescreva o método interno centavos (o da Arredonda, que o módulo esconde) para arredondar a duas casas: multiplique por 100, arredonde com Math.round e divida por 100.0. A saída deve ser 2.5, 3.14 e 10.0.
public class Main {
static double centavos(double valor) {
return valor;
}
public static void main(String[] args) {
System.out.println(centavos(2.499));
System.out.println(centavos(3.14159));
System.out.println(centavos(10.0));
}
}
public class Main {
static double centavos(double valor) {
return Math.round(valor * 100) / 100.0;
}
public static void main(String[] args) {
System.out.println(centavos(2.499));
System.out.println(centavos(3.14159));
System.out.println(centavos(10.0));
}
}
6. Quando vale a pena usar módulos?
Com franqueza: a maioria dos projetos Java não usa módulos. Aplicações web, sistemas de empresas e projetos com Maven ou Gradle continuam no classpath, e funcionam muito bem. Muitas bibliotecas populares ainda não trazem module-info, e misturar as duas coisas exige cuidado. Você não precisa de módulos para escrever bons programas.
| Situação | Módulos? |
|---|---|
| Aprendendo Java, programas pequenos, exercícios | Não |
| Aplicação comum com Maven/Gradle e várias bibliotecas | Geralmente não |
| Você escreve uma biblioteca para outras pessoas e quer esconder os pacotes internos | Sim, vale a pena |
Você quer entregar um programa com um Java enxuto (jlink/jpackage), por exemplo um aplicativo de desktop | Sim |
| Um sistema grande, com equipes diferentes, em que as fronteiras entre as partes precisam ser respeitadas | Pode valer |
Mesmo sem escrever module-info, é bom saber que os módulos existem: eles explicam mensagens como is not visible, o java.base que aparece nos stack traces (at java.base/java.util…) e a organização da documentação oficial, que é dividida por módulo.
Quando dá errado
package … is not visible
O que aconteceu: leia a linha entre parênteses. which does not export it: o pacote não está nos exports do outro módulo (ele é interno; use outra classe, ou exporte o pacote se o módulo for seu). does not read it: falta o requires no seu module-info.java.
No matching locales found for "pt-BR"
$ jlink --module-path out --add-modules br.com.ana.loja,jdk.localedata --include-locales=pt-BR --output caixa-java-br --strip-debug --no-header-files --no-man-pages --launcher caixa=br.com.ana.loja/br.com.ana.loja.Caixa Error: No matching locales found for "pt-BR". Check the specified pattern.
O que aconteceu: neste JDK, o jlink não achou o padrão pt-BR escrito assim. Como resolver: use --include-locales='pt-*' (todo o português), como na seção 5.
jlink
O que aconteceu: faltou o módulo jdk.localedata. Como resolver: --add-modules …,jdk.localedata (de preferência com --include-locales para não levar todos os idiomas). Use caixa-java/bin/java --list-modules para conferir o que foi incluído.
Module … not found ao rodar
$ java -p mods/loja.jar -m br.com.ana.loja 1 Error occurred during initialization of boot layer java.lang.module.FindException: Module br.com.ana.util not found, required by br.com.ana.loja
O que aconteceu: o module path (-p) tem só o loja.jar; o br.com.ana.util, que ele exige, não está lá. Ao contrário do classpath, o Java confere antes de começar (during initialization) e se recusa a rodar sem todos os requires. Como resolver: aponte o -p para a pasta com todos os módulos (-p mods).
Referência
| Item | O que faz |
|---|---|
module nome { … } | Declara o módulo (module-info.java, na raiz dos fontes) |
requires modulo; | Este módulo usa aquele |
exports pacote; | Este pacote pode ser usado por outros módulos |
javac -p out -d out/mod $(find src/mod -name "*.java") | Compila um módulo usando outros já compilados |
javac -d out --module-source-path src --module a,b | Compila vários módulos de uma vez |
java -p out -m modulo/pacote.Classe | Roda pelo module path |
jar --create --file x.jar --main-class … -C pasta . | Cria um JAR modular |
java --list-modules / --describe-module m | Lista os módulos / mostra um |
jdeps -s … / jdeps --print-module-deps … | Dependências de classes ou JARs |
jlink --module-path … --add-modules … --output … | Cria um Java sob medida |
Exercícios
1. Exportar um pacote
Complete o module-info.java do módulo br.com.ana.agenda para que outros módulos usem o pacote br.com.ana.agenda.modelo:
}
2. Usar um módulo do JDK
O seu módulo usa java.sql.Connection, do módulo java.sql. Complete:
3. Rodar
Complete para rodar a classe br.com.ana.agenda.App do módulo br.com.ana.agenda, com os módulos na pasta mods:
4. A classe Arredonda é public, mas o pacote dela não está nos exports. Outro módulo consegue usá-la?
5. Para que serve o jlink?
6. Qual módulo todo módulo recebe sem precisar de requires?
Resumo
- Um módulo agrupa pacotes e declara, no
module-info.java, o que usa (requires) e o que mostra (exports). Ojava.basevem sempre. - Pacote não exportado fica invisível fora do módulo, mesmo com classes
public. - Compilar e rodar:
javac -p … -d …ejava -p … -m modulo/classe. - Erros
is not visible:does not export it(faltaexportsno outro) oudoes not read it(faltarequiresno seu). jdepsmostra as dependências;jlinkcria um Java sob medida (aqui, 56 MB contra 303 MB). Incluajdk.localedatase precisar de formatos do Brasil.- O classpath continua funcionando; a maioria dos projetos não usa módulos. Eles valem a pena em bibliotecas e em programas entregues com
jlink.