W3docs

Dependências Maven no Java

Declare e gerencie dependências Java no Maven com dependency, scope e resolução transitiva.

Projetos Java reais raramente existem sozinhos. Eles utilizam frameworks de logging, clientes HTTP, parsers JSON e bibliotecas de testes escritas por outras pessoas. O papel do Maven é buscar essas bibliotecas, buscar as dependências delas por sua vez, e montar um único classpath consistente sem que você precise rastrear um único JAR manualmente. Entender como ele faz isso é a diferença entre uma build que simplesmente funciona e uma tarde perdida com um NoSuchMethodError.

Este capítulo aborda como uma dependência é identificada (coordenadas), como os escopos controlam onde cada biblioteca é visível, como o Maven percorre o grafo de dependências transitivas e como ele resolve conflitos de versão. Pressupõe que você já tem um pom.xml; caso contrário, comece pelo capítulo Maven POM.

Coordenadas: Como uma Dependência É Identificada

Todo artefato no mundo Maven é identificado por um conjunto de coordenadas. As três que você sempre fornece são o groupId (quem o publica, geralmente um domínio invertido), o artifactId (o nome do projeto) e a version. Juntas, elas apontam para exatamente um JAR em um repositório.

Você declara uma dependência dentro do bloco <dependencies> do seu pom.xml:

<dependencies>
  <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.14.0</version>
  </dependency>
</dependencies>

A forma abreviada groupId:artifactId:version é chamada de string GAV, e você a verá em todo lugar: em mensagens de erro, na árvore de dependências e nas páginas do repositório central. Uma quarta coordenada, o type (jar por padrão), e uma quinta, o classifier (para variantes como sources ou javadoc), completam o endereço completo.

Escopos: Quando uma Dependência É Visível

Nem toda dependência pertence a todos os classpaths. Um framework de testes não deve ser incluído no seu JAR de produção, e uma API de servlet fornecida pelo servidor de aplicação não deve ser empacotada duas vezes. O Maven controla isso com o elemento <scope>.

EscopoCompilaçãoTesteExecuçãoEmpacotadoUso típico
compile (padrão)SimSimSimSimBibliotecas principais chamadas diretamente
providedSimSimNãoNãoAPIs fornecidas pelo contêiner (servlet, driver JDBC)
runtimeNãoSimSimSimImplementações necessárias apenas em tempo de execução
testNãoSimNãoNãoJUnit, Mockito, bibliotecas de asserção
systemSimSimNãoNãoJARs locais por caminho absoluto (evitar)

Uma dependência com escopo test é a mais comum fora do padrão. O JUnit nunca vazará para o seu artefato distribuído:

<dependency>
  <groupId>org.junit.jupiter</groupId>
  <artifactId>junit-jupiter</artifactId>
  <version>5.11.0</version>
  <scope>test</scope>
</dependency>

Dependências Transitivas

Quando você depende de uma biblioteca, também depende de tudo aquilo de que ela depende. O Maven lê o pom.xml publicado de cada artefato, segue essas declarações recursivamente e adiciona o grafo inteiro ao seu classpath automaticamente. Essas entradas indiretas são dependências transitivas.

É por isso que uma única linha <dependency> para um framework web pode trazer dezenas de JARs que você nunca nomeou. O escopo ainda se aplica durante essa varredura: uma dependência com escopo test não puxa suas dependências transitivas para o seu classpath de compilação, e dependências provided não são propagadas transitivamente.

Você pode ver o grafo completo com o plugin de dependências:

$ mvn dependency:tree
[INFO] com.example:app:jar:1.0
[INFO] +- org.web:server:jar:2.4:compile
[INFO] |  +- org.log:log:jar:1.2:compile
[INFO] |  \- org.json:json:jar:1.7:compile - omitted for conflict with 1.9
[INFO] \- org.json:json:jar:1.9:compile

Mediação de Conflito de Versão

Um grafo tão profundo quase sempre requer o mesmo artefato em duas versões diferentes. O Maven não pode colocar ambas em um único classpath, então escolhe uma usando a mediação nearest-wins (o mais próximo vence): a versão declarada na profundidade mais rasa a partir da raiz do projeto prevalece, e as demais são omitidas por conflito.

Na árvore acima, seu projeto solicita org.json:json:1.9 diretamente (profundidade 1), enquanto org.web:server solicita 1.7 transitivamente (profundidade 2). A declaração na profundidade 1 vence. Se dois candidatos estiverem na mesma profundidade, o declarado primeiro no pom.xml vence.

Quando a escolha automática está errada, você assume o controle explicitamente. Uma dependência direta sempre vence, ou você pode fixar versões em todo o projeto com <dependencyManagement>:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.json</groupId>
      <artifactId>json</artifactId>
      <version>1.9</version>
    </dependency>
  </dependencies>
</dependencyManagement>

Para eliminar completamente um ramo transitivo indesejado, use <exclusions>:

<dependency>
  <groupId>org.web</groupId>
  <artifactId>server</artifactId>
  <version>2.4</version>
  <exclusions>
    <exclusion>
      <groupId>org.json</groupId>
      <artifactId>json</artifactId>
    </exclusion>
  </exclusions>
</dependency>

Um Exemplo Completo

O Maven em si não está disponível neste executor de código, então o programa abaixo modela seu resolvedor em Java puro. Ele publica alguns artefatos em um pequeno repositório em memória e, em seguida, realiza a mesma varredura em largura e mediação nearest-wins que o Maven usa para aplainar um grafo de dependências em um único classpath. Observe como o org.json:json:1.7 mais profundo perde para o 1.9 mais raso.

java— editable, runs on the server

O que observar na execução:

  • O classpath resolvido lista cada artefato exatamente uma vez, espelhando como o Maven achata um grafo em um único conjunto de JARs sem entradas duplicadas de group:artifact.
  • org.json:json aparece na versão 1.9, não 1.7, porque a mediação nearest-wins mantém o candidato encontrado na profundidade mais rasa (profundidade 1 vence sobre profundidade 2).
  • A coluna depth torna "mais próximo" concreto: app:app está na profundidade 0, suas dependências diretas estão na profundidade 1, e org.log:log puxado transitivamente está na profundidade 2.
  • "Tree edges visited: 4" conta os relacionamentos de dependência declarados, enquanto "Distinct artifacts: 4" mostra o grafo reduzido a quatro coordenadas únicas após a mediação.
  • Uma coordenada é ignorada no momento em que é vista novamente (depthOf.containsKey(ga)), o que é exatamente o motivo pelo qual o 1.7 mais profundo é "omitido por conflito" em vez de ser adicionado uma segunda vez.

Uma vez que suas dependências se resolvam corretamente, a próxima pergunta é quando o Maven baixa, compila, testa e empacota. Essa ordenação é governada pelas fases de build abordadas no capítulo ciclo de vida do Maven.

Prática

Prática
No Maven, duas versões do mesmo artefato aparecem no grafo de dependências: versão 1.9 declarada diretamente no pom (profundidade 1) e versão 1.7 puxada transitivamente (profundidade 2). Qual versão acaba no classpath?
No Maven, duas versões do mesmo artefato aparecem no grafo de dependências: versão 1.9 declarada diretamente no pom (profundidade 1) e versão 1.7 puxada transitivamente (profundidade 2). Qual versão acaba no classpath?
Was this page helpful?