1. Problemas da abordagem antiga
“Classpath party”: quando todos na mesma cozinha
Antes do surgimento dos módulos no Java (na versão 9), todo o código do aplicativo, bibliotecas e dependências caía em um grande monte — o classpath. Praticamente não havia limites entre bibliotecas: qualquer classe que entrasse no classpath podia ser encontrada e usada por qualquer um.
Surgiam conflitos de nomes: duas bibliotecas contêm uma classe com o mesmo nome totalmente qualificado, por exemplo com.example.Util — “alguma delas” era escolhida, e você só descobria o problema pelo comportamento estranho em tempo de execução.
Clássico do gênero — dependency hell: uma biblioteca exige um logger versão 1.2, outra — 1.3, e no classpath acabam ficando ambas. A JVM não sabe qual carregar, e você encontra um enigmático NoSuchMethodError.
E mais: mesmo que uma classe seja declarada como public, mas destinada ao uso interno da biblioteca, outro código ainda pode usá-la — e acabar quebrando algo no processo. Projetos grandes viravam um campo minado, e a manutenção — uma aventura.
2. O que é um módulo no Java?
Módulo: como uma caixa com orifícios
No Java 9 surgiu o sistema de módulos (JPMS). Um módulo é uma caixa com classes e pacotes na qual você mesmo faz “orifícios”: indica explicitamente o que é exposto para fora (exports) e o que fica escondido dentro. E sim: mesmo uma classe public não fica visível para outros módulos se o seu pacote não for exportado.
Definição formal
Um módulo é uma unidade lógica de organização de código que reúne pacotes e classes relacionados. Cada módulo declara explicitamente:
- o que ele exporta — torna disponível para outros módulos (exports);
- e o que ele importa — de quais outros módulos depende (requires).
Propriedades-chave do módulo:
- Fronteiras explícitas. Fica claramente definido o que está acessível de fora e o que não está.
- Dependências explícitas. Um módulo não “enxerga” outro módulo sem um requires explícito.
- Isolamento. Detalhes internos podem ser totalmente ocultados do mundo externo.
3. Vantagens da modularidade
Encapsulamento aprimorado
Surgiu um novo nível de visibilidade — no nível do módulo. Mesmo que a classe seja public, se o seu pacote não for exportado, ela só é visível dentro do módulo. O interior da biblioteca não “vaza” mais para fora.
Declaração explícita de dependências
As dependências necessárias são listadas em module-info.java. O compilador e a IDE avisam com antecedência se você esquecer de declarar um módulo do qual depende.
Segurança e confiabilidade aprimoradas
Restringir o acesso a APIs internas dificulta a interferência acidental ou intencional. Isso é crítico para grandes bibliotecas e módulos de plataforma.
Possibilidade de criar uma JRE “enxuta”
Com o jlink é possível montar um runtime mínimo apenas com os módulos necessários. Em nuvem e cenários embedded isso economiza espaço em disco e memória.
Bônus: inicialização mais rápida e menor tamanho
Não são carregados todos os módulos, mas apenas os realmente usados — os aplicativos iniciam mais rápido e consomem menos recursos.
4. Onde os módulos são usados
Na biblioteca padrão do Java
A partir do Java 9, a própria plataforma é modular. Exemplos:
- java.base — módulo básico (obrigatório para qualquer programa).
- java.sql — trabalho com bancos de dados.
- java.xml — trabalho com XML.
Se você não usa XML, o módulo java.xml nem entrará no seu runtime.
Em aplicativos e bibliotecas grandes
Must-have para sistemas corporativos, onde dezenas de equipes desenvolvem partes de um monorepositório. A modularidade reduz conflitos e simplifica a manutenção.
Em projetos próprios
Mesmo em um pet project, os módulos ajudam a treinar o pensamento arquitetural e manter o código em ordem, evitando “espaguete”.
5. Resumo rápido da sintaxe: module-info.java
O mais importante — o arquivo module-info.java
O arquivo module-info.java fica na raiz dos fontes do módulo e declara seus limites e dependências.
module my.awesome.module {
exports com.example.api; // Pacote exportado
requires java.sql; // Dependência de um módulo padrão
}
Principais palavras-chave:
- module <nome> — declara um módulo.
- exports <pacote> — torna um pacote disponível para outros módulos.
- requires <módulo> — declara dependência de outro módulo.
Exemplo mínimo:
module com.myproject.core {
exports com.myproject.core.api;
}
Exemplo com dependência:
module com.myproject.app {
requires com.myproject.core;
requires java.sql;
}
Recursos adicionais (breve): para reflexão existe opens, para serviços — uses e provides ... with ... (detalhes — em aulas avançadas).
6. Dicas úteis
Módulos são como “passaportes” para classes. Antes, qualquer classe com o visto public podia “viajar” pelo aplicativo. Agora, também é preciso o “passaporte” modular — a exportação do pacote (exports). Sem ela, a classe fica “em casa”.
Dependency hell é um termo real. Se você já viu ClassNotFoundException: com.google.common.base.Strings, você já esteve lá. Os módulos foram feitos para espantar esses “demônios” com fronteiras e dependências estritas.
Todo o Java agora é modular. Mesmo que você não escreva seus próprios módulos, a plataforma já está dividida em dezenas. Tente o comando:
java --list-modules
Como isso impacta o desenvolvimento
Você gerencia explicitamente a visibilidade do código, e os pacotes internos deixam de ficar acessíveis a todos “por acaso”. A IDE e o compilador capturam erros mais cedo: esqueceu de declarar uma dependência — o projeto não compila. A manutenção de sistemas grandes fica mais simples: é mais fácil entender quem depende de quem e o que pode quebrar com mudanças.
7. Erros comuns ao migrar para módulos
Erro nº 1: exportou o pacote? A classe é public, mas... Você declarou uma classe como public, porém não exportou seu pacote — outros módulos não conseguirão usá-la. O compilador informará: “O pacote não é exportado pelo módulo”.
Erro nº 2: esqueceu de declarar a dependência com requires. Você usa um tipo de outro módulo, mas esqueceu de adicionar requires no module-info.java. Resultado — erro de compilação: “o módulo não consegue encontrar a classe necessária”.
Erro nº 3: nomes de módulos duplicados. Em um projeto grande, dois módulos acidentalmente receberam o mesmo nome. A JVM não perdoa isso — renomeie e adote um padrão de nomenclatura consistente.
Erro nº 4: esquecer dos módulos padrão. Por exemplo, você usa JDBC, mas não adicionou requires java.sql;. No Java 8 “já era visível”, mas no 9+ — não.
Erro nº 5: tentativa de usar uma classe interna de outro módulo. Se o pacote não é exportado, mesmo uma classe public permanece invisível de fora. Exporte o pacote ou mova o API para uma camada “pública”.
GO TO FULL VERSION