Java Maven Build
Módulo 2 — Java

Maven & Gestão de Dependências

Em C, quando o teu programa precisa de uma biblioteca externa, tens de a descarregar manualmente, colocá-la no sítio certo, e dizer ao compilador onde a encontrar. Java empresarial resolve este problema com o Maven — uma ferramenta que gere dependências, automatiza o processo de build, e garante que todos os developers de uma equipa compilam o projecto exactamente da mesma forma.

O Problema que o Maven Resolve

Imagina que a tua aplicação Spring Boot precisa de: o framework Spring, o driver JDBC do PostgreSQL, a biblioteca BCrypt para hashing de passwords, e o Jackson para serializar JSON. Cada uma destas bibliotecas tem a sua própria versão. Algumas dependem de outras bibliotecas — que por sua vez dependem de outras. Sem uma ferramenta de gestão, tens de resolver esta árvore de dependências manualmente, descarregar cada JAR, e garantir que não há conflitos de versões.

O Maven resolve isto declarativamente: dizes o que precisas no ficheiro pom.xml, e o Maven trata de descarregar, versionar e colocar tudo no classpath correcto. Na primeira vez que compilas, os JARs são descarregados automaticamente. Nas vezes seguintes, estão em cache local.

Anatomia do pom.xml

O pom.xml (Project Object Model) é o ficheiro de configuração central de qualquer projecto Maven. Define a identidade do projecto, as suas dependências, os plugins de build, e como deve ser empacotado.

<!-- pom.xml — estrutura essencial -->
<project>

  <!-- Identidade do projecto -->
  <groupId>pt.randomt</groupId>        <!-- organização / pacote raiz -->
  <artifactId>autoflow</artifactId>   <!-- nome do projecto -->
  <version>1.0.0</version>            <!-- versão actual -->
  <packaging>jar</packaging>          <!-- artefacto final: JAR -->

  <!-- Parent: herda o BOM e configurações base do Spring Boot -->
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.3.0</version>
  </parent>

  <properties>
    <java.version>17</java.version>
  </properties>

  <dependencies>
    <!-- dependências aqui -->
  </dependencies>

  <build>
    <plugins>
      <!-- plugins de build aqui -->
    </plugins>
  </build>

</project>

Dependências e Scopes

Cada dependência tem um scope — define em que fase do ciclo de vida a biblioteca está disponível.

ScopeCompileTestRuntime (JAR)Quando usar
compile (default) Bibliotecas de que a aplicação depende em runtime. Spring, Jackson, BCrypt.
test Apenas para testes. JUnit, Mockito, TestContainers. Nunca entram no JAR final.
provided O container já fornece a biblioteca. Lombok (processado em compile-time).
runtime Necessária em runtime mas não para compilar. Driver JDBC do PostgreSQL.
<dependencies>

  <!-- Spring Boot Web — scope compile (default) -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <!-- Driver PostgreSQL — scope runtime -->
  <!-- o código compila sem ele; a JVM precisa dele em execução -->
  <dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <scope>runtime</scope>
  </dependency>

  <!-- JUnit — scope test. Nunca entra no JAR de produção -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
  </dependency>

</dependencies>

Dependências Transitivas

Quando declaras uma dependência, essa biblioteca pode ela própria depender de outras — as dependências transitivas. Entram automaticamente no classpath, o que é conveniente mas pode criar conflitos de versões.

# Ver a árvore completa de dependências transitivas
mvn dependency:tree

# Exemplo de output:
# pt.randomt:autoflow:jar:1.0.0
# +- org.springframework.boot:spring-boot-starter-web
# |  +- org.springframework.boot:spring-boot-starter
# |  |  \- com.fasterxml.jackson.core:jackson-databind:2.17.0  <- transitiva
# |  \- org.springframework:spring-webmvc
# \- org.postgresql:postgresql (runtime)

Quando uma dependência transitiva causa problemas, podes excluí-la explicitamente:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <exclusions>
    <!-- excluir Tomcat — usar Undertow em vez disso -->
    <exclusion>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
    </exclusion>
  </exclusions>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

SNAPSHOT vs RELEASE

SNAPSHOT
1.0.0-SNAPSHOT

Versão em desenvolvimento — mutável. O Maven verifica sempre se existe uma versão mais recente no repositório remoto, mesmo que já exista em cache. Usada durante desenvolvimento activo.

RELEASE
1.0.0

Versão imutável. Uma vez publicada, nunca muda. O Maven usa o cache local e nunca volta ao repositório remoto para a mesma versão. Usada em produção e em pipelines de CI/CD.

Nunca usar SNAPSHOT em produção Uma dependência SNAPSHOT pode mudar de um build para o outro. Todas as dependências em produção devem ser versões RELEASE imutáveis.

Repositórios: Local, Central e Privado

Quando o Maven precisa de uma dependência, resolve-a seguindo uma hierarquia — do mais local ao mais remoto. Se existir em cache local, não vai à rede.

# 1. Repositório local — cache na tua máquina
~/.m2/repository/
#    Se existir aqui, não vai à rede.

# 2. Maven Central — repositório público global
#    https://repo.maven.apache.org/maven2

# 3. Repositório privado (empresarial) — opcional
#    Nexus ou Artifactory. Para bibliotecas proprietárias.
<repositories>
  <repository>
    <id>randomt-nexus</id>
    <url>https://nexus.randomt.pt/repository/maven-releases/</url>
  </repository>
</repositories>

Plugins: Quem Realmente Faz o Build

O Maven em si quase não faz nada — quem executa cada fase são os plugins. O maven-compiler-plugin compila o código. O maven-surefire-plugin corre os testes. O spring-boot-maven-plugin produz o fat JAR. Com spring-boot-starter-parent, estes plugins já estão configurados por omissão.

<build>
  <plugins>

    <!-- Compilador Java -->
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <configuration>
        <source>17</source>
        <target>17</target>
      </configuration>
    </plugin>

    <!-- Spring Boot Maven Plugin — produz o fat JAR        -->
    <!-- SEM este plugin, mvn package produz um JAR simples -->
    <!-- sem dependências — não executável directamente     -->
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>

  </plugins>
</build>
Fat JAR vs JAR simples Um JAR simples contém apenas o bytecode do teu projecto — as dependências ficam de fora. Um fat JAR empacota tudo num único ficheiro executável com java -jar. É a diferença entre um artefacto que "simplesmente funciona" e um que requer configuração adicional no servidor de destino.

O Ciclo de Vida do Build

# Verificar erros de compilação — não corre testes
mvn compile

# Compilar + correr todos os testes unitários
mvn test

# Compilar + testar + empacotar → target/autoflow-1.0.0.jar
mvn package

# Build limpo — apaga target/ antes de empacotar
# Usar sempre antes de distribuir
mvn clean package

# Empacotar sem correr testes
mvn clean package -DskipTests

# Instalar no repositório local (~/.m2)
# Necessário quando outro projecto Maven local depende deste
mvn install

# Publicar num repositório remoto (Nexus/Artifactory)
# Usado em pipelines de CI/CD para publicar releases
mvn deploy
Nunca distribuir sem clean Sem mvn clean, ficheiros antigos em target/ podem contaminar o artefacto — classes removidas podem persistir no JAR. Usa sempre mvn clean package antes de distribuir.

Profiles: Configurações por Ambiente

Os profiles permitem activar configurações diferentes por ambiente sem alterar o pom.xml principal.

<profiles>
  <profile>
    <id>dev</id>
    <activation>
      <activeByDefault>true</activeByDefault>
    </activation>
    <properties>
      <spring.profiles.active>dev</spring.profiles.active>
    </properties>
  </profile>

  <profile>
    <id>prod</id>
    <properties>
      <spring.profiles.active>prod</spring.profiles.active>
    </properties>
  </profile>
</profiles>
# Activar um profile explicitamente
mvn clean package -P prod

# O Spring Boot carrega application-prod.properties automaticamente

O Maven Wrapper (mvnw)

Projectos gerados pelo Spring Initializr incluem mvnw (Linux/macOS) e mvnw.cmd (Windows). O Maven Wrapper descarrega automaticamente a versão correcta do Maven sem instalação prévia — garante que toda a equipa e o CI/CD usam exactamente a mesma versão.

# Em vez de mvn, usar o wrapper:
./mvnw clean package        # Linux / macOS
mvnw.cmd clean package      # Windows

# A versão usada está em .mvn/wrapper/maven-wrapper.properties:
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip
Versionar o wrapper, não o Maven Os ficheiros mvnw, mvnw.cmd e .mvn/ devem estar no Git. Qualquer developer que clone o repositório compila imediatamente sem instalar o Maven manualmente.

O BOM: Versões Geridas Centralmente

Se não puderes usar o spring-boot-starter-parent como parent (porque o projecto já tem um parent corporativo), importa o BOM directamente via dependencyManagement:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-dependencies</artifactId>
      <version>3.3.0</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

<!-- Dependências Spring sem versão explícita — geridas pelo BOM -->
<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
</dependencies>

Dependências e Segurança

Cada dependência é código de terceiros que corre na tua aplicação. O Log4Shell (CVE-2021-44228, Dezembro 2021) é o exemplo mais citado: vulnerabilidade crítica na Log4j que permitia execução remota de código arbitrário, afectando milhares de aplicações Java. A mitigação era actualizar uma linha no pom.xml — mas primeiro era preciso saber que a dependência estava lá.

# Identificar dependências transitivas
mvn dependency:tree

# Verificar CVEs conhecidas (requer plugin OWASP no pom.xml)
mvn dependency-check:check

# Ver versões mais recentes disponíveis
mvn versions:display-dependency-updates
<!-- Plugin OWASP Dependency Check -->
<plugin>
  <groupId>org.owasp</groupId>
  <artifactId>dependency-check-maven</artifactId>
  <version>9.0.9</version>
  <configuration>
    <!-- falhar o build se CVSS score >= 7 -->
    <failBuildOnCVSS>7</failBuildOnCVSS>
  </configuration>
</plugin>
Dependências desactualizadas são superfície de ataque Integrar o OWASP Dependency Check no pipeline de CI/CD garante que nenhuma release passa com vulnerabilidades críticas conhecidas. Actualizar dependências é manutenção contínua, não uma tarefa pontual.

Estrutura de Directorias Maven

autoflow/
├── pom.xml                               # configuração Maven
├── mvnw                                  # Maven Wrapper (Linux/macOS)
├── mvnw.cmd                              # Maven Wrapper (Windows)
├── .mvn/wrapper/maven-wrapper.properties # versão do Maven
├── src/
│   ├── main/
│   │   ├── java/pt/randomt/autoflow/     # código fonte
│   │   └── resources/
│   │       ├── application.properties
│   │       ├── application-dev.properties
│   │       └── application-prod.properties
│   └── test/java/pt/randomt/autoflow/    # testes
└── target/                               # gerado — não versionar (.gitignore)
    └── autoflow-1.0.0.jar

Checklist Maven