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.
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.
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>
Cada dependência tem um scope — define em que fase do ciclo de vida a biblioteca está disponível.
| Scope | Compile | Test | Runtime (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>
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>
1.0.0-SNAPSHOTVersã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.
1.0.0Versã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.
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>
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>
java -jar.
É a diferença entre um artefacto que "simplesmente funciona" e um que requer
configuração adicional no servidor de destino.
# 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
mvn clean, ficheiros antigos em target/ podem
contaminar o artefacto — classes removidas podem persistir no JAR.
Usa sempre mvn clean package antes de distribuir.
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
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
mvnw, mvnw.cmd e .mvn/
devem estar no Git. Qualquer developer que clone o repositório compila
imediatamente sem instalar o Maven manualmente.
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>
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>
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
spring-boot-starter-parent como parent, ou importar o BOM via dependencyManagement.test para JUnit/Mockito, runtime para o driver JDBC.mvn clean package para builds de distribuição — nunca sem clean.mvnw e .mvn/ — ignorar target/ no .gitignore.mvn dependency:tree quando aparecem erros de classpath.mvn versions:display-dependency-updates antes de cada release.