Maven POM structure, dependency management, plugin configuration, lifecycle phases, multi-module projects, profiles, and repository management for Java projects.
Last Updated: 2025-10-26
Use this skill when:
Alternatives: Gradle (flexibility), Bazel (monorepos), Ant (legacy)
Prerequisites: JDK 8+ installed, Maven 3.9+ (3.9.6 latest as of 2024)
<!-- pom.xml - Minimum viable POM -->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- Coordinates -->
<groupId>com.example</groupId>
<artifactId>myapp</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<!-- Metadata -->
<name>My Application</name>
<description>Sample Maven project</description>
<!-- Properties -->
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<!-- Dependencies -->
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
project/
├── pom.xml
├── src/
│ ├── main/
│ │ ├── java/ # Java source files
│ │ ├── resources/ # Application resources
│ │ └── webapp/ # Web application files (WAR)
│ └── test/
│ ├── java/ # Test source files
│ └── resources/ # Test resources
└── target/ # Build output (gitignored)
├── classes/
├── test-classes/
└── myapp-1.0.0.jar
<dependencies>
<!-- Compile + Runtime dependency -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.9</version>
</dependency>
<!-- Runtime only -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.6.0</version>
<scope>runtime</scope>
</dependency>
<!-- Test only -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
<!-- Provided (e.g., servlet API from container) -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<!-- Scopes determine classpath availability:
compile: Default, all classpaths
provided: Compile + test, not runtime (container provides)
runtime: Runtime + test, not compile
test: Test only
system: Similar to provided, but manual path (avoid)
import: Only for <dependencyManagement>, imports BOM
-->
<!-- Parent or root POM -->
<dependencyManagement>
<dependencies>
<!-- Import Spring Boot BOM -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.2.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Or manage versions directly -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
</dependency>
</dependencies>
</dependencyManagement>
<!-- Child modules can omit version -->
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<!-- Version inherited from dependencyManagement -->
</dependency>
</dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>mylib</artifactId>
<version>1.0.0</version>
<!-- Exclude transitive dependency -->
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Override transitive version -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
</dependency>
Maven has 3 built-in lifecycles:
validate → compile → test → package → verify → install → deploy
# Execute phases (runs all previous phases)
mvn validate # Validate project structure
mvn compile # Compile source code
mvn test # Run unit tests
mvn package # Create JAR/WAR
mvn verify # Run integration tests
mvn install # Install to local repository (~/.m2)
mvn deploy # Deploy to remote repository
# Clean lifecycle
mvn clean # Delete target/ directory
# Combined
mvn clean install # Clean then install
mvn clean verify # Clean then verify (common in CI)
<!-- Plugins bind goals to lifecycle phases -->
<build>
<plugins>
<!-- Compiler plugin (binds to compile phase) -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
<!-- Surefire plugin (binds to test phase) -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.2</version>
</plugin>
</plugins>
</build>
<!-- Execute specific plugin goal -->
<!-- mvn compiler:compile -->
<!-- mvn surefire:test -->
<build>
<plugins>
<!-- Compiler Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<release>17</release>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
<!-- Surefire (Unit Tests) -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<includes>
<include>**/*Test.java</include>
</includes>
</configuration>
</plugin>
<!-- Failsafe (Integration Tests) -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.2.2</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- JAR Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<mainClass>com.example.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<!-- Assembly (Fat JAR) -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>com.example.Main</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Shade (Fat JAR with relocation) -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.example.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<!-- Build: mvn spring-boot:run -->
<!-- Package: mvn package (creates executable JAR) -->
multi-module/
├── pom.xml # Parent POM
├── core/
│ └── pom.xml
├── api/
│ └── pom.xml
└── app/
└── pom.xml
<!-- multi-module/pom.xml -->
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>multi-module-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<!-- Declare modules -->
<modules>
<module>core</module>
<module>api</module>
<module>app</module>
</modules>
<!-- Common properties -->
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.version>5.10.0</junit.version>
</properties>
<!-- Dependency management (versions) -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- Common dependencies for all modules -->
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
<!-- multi-module/app/pom.xml -->
<project>
<modelVersion>4.0.0</modelVersion>
<!-- Parent reference -->
<parent>
<groupId>com.example</groupId>
<artifactId>multi-module-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>app</artifactId>
<!-- Module dependencies -->
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>api</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
# Build all modules from root
mvn clean install
# Build specific module
mvn clean install -pl app
# Build module and dependencies
mvn clean install -pl app -am
# Build without dependencies
mvn clean install -pl app -DskipTests
# Build in parallel
mvn clean install -T 4 # 4 threads
<profiles>
<!-- Development profile -->
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<env>development</env>
<db.url>jdbc:h2:mem:testdb</db.url>
</properties>
</profile>
<!-- Production profile -->
<profile>
<id>prod</id>
<properties>
<env>production</env>
<db.url>jdbc:postgresql://prod-db:5432/app</db.url>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<debug>false</debug>
<optimize>true</optimize>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<!-- Platform-specific -->
<profile>
<id>windows</id>
<activation>
<os>
<family>windows</family>
</os>
</activation>
<properties>
<script.extension>.bat</script.extension>
</properties>
</profile>
</profiles>
<!-- Activate profile: mvn clean install -Pprod -->
<!-- Multiple profiles: mvn clean install -Pdev,docker -->
<repositories>
<!-- Maven Central (default, can omit) -->
<repository>
<id>central</id>
<url>https://repo.maven.apache.org/maven2</url>
</repository>
<!-- Custom repository -->
<repository>
<id>company-repo</id>
<url>https://repo.company.com/maven</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<!-- Plugin repositories -->
<pluginRepositories>
<pluginRepository>
<id>spring-plugins</id>
<url>https://repo.spring.io/plugins-release</url>
</pluginRepository>
</pluginRepositories>
<!-- ~/.m2/settings.xml -->
<settings>
<!-- Local repository location -->
<localRepository>${user.home}/.m2/repository</localRepository>
<!-- Mirrors -->
<mirrors>
<mirror>
<id>company-mirror</id>
<url>https://nexus.company.com/repository/maven-public</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
<!-- Servers (authentication) -->
<servers>
<server>
<id>company-repo</id>
<username>deploy-user</username>
<password>{encrypted-password}</password>
</server>
</servers>
<!-- Active profiles -->
<activeProfiles>
<activeProfile>company</activeProfile>
</activeProfiles>
</settings>
<!-- WRONG: Duplicate versions across modules -->
<!-- module1/pom.xml -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
</dependency>
<!-- module2/pom.xml -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.0-jre</version> <!-- Different version! -->
</dependency>
<!-- CORRECT: Centralize in parent POM -->
<!-- parent/pom.xml -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
</dependency>
</dependencies>
</dependencyManagement>
<!-- WRONG -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.2.0</version>
</dependency>
<!-- CORRECT: Use properties -->
<properties>
<spring-boot.version>3.2.0</spring-boot.version>
</properties>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- WRONG: Unversioned plugin (non-reproducible builds) -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<!-- CORRECT: Pin versions -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
</plugin>
<!-- WRONG: Version in both places -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.0-jre</version> <!-- Override defeats purpose -->
</dependency>
</dependencies>
<!-- CORRECT: Version only in dependencyManagement -->
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<!-- Inherits version from dependencyManagement -->
</dependency>
</dependencies>
# Lifecycle
mvn clean # Clean target/
mvn compile # Compile sources
mvn test # Run unit tests
mvn package # Create JAR/WAR
mvn verify # Run integration tests
mvn install # Install to local repo
mvn deploy # Deploy to remote repo
# Combined
mvn clean install # Most common workflow
mvn clean verify # CI workflow
mvn clean package -DskipTests # Fast build
# Multi-module
mvn install -pl app # Build specific module
mvn install -pl app -am # Build with dependencies
mvn install -T 4 # Parallel build (4 threads)
# Profiles
mvn install -Pprod # Activate profile
# Information
mvn dependency:tree # Show dependency tree
mvn dependency:analyze # Analyze dependencies
mvn help:effective-pom # Show effective POM
mvn versions:display-dependency-updates # Check updates
<groupId>com.example</groupId> <!-- Organization/group -->
<artifactId>myapp</artifactId> <!-- Project name -->
<version>1.0.0-SNAPSHOT</version> <!-- Version -->
<packaging>jar</packaging> <!-- jar, war, pom, etc. -->
# generate_dependencies.py - Generate Maven dependencies from JSON
import json
import xml.etree.ElementTree as ET
with open('dependencies.json') as f:
deps = json.load(f)
dependencies = ET.Element('dependencies')
for dep in deps:
dependency = ET.SubElement(dependencies, 'dependency')
ET.SubElement(dependency, 'groupId').text = dep['group']
ET.SubElement(dependency, 'artifactId').text = dep['name']
ET.SubElement(dependency, 'version').text = dep['version']
if 'scope' in dep:
ET.SubElement(dependency, 'scope').text = dep['scope']
print(ET.tostring(dependencies, encoding='unicode'))
gradle-jvm-builds.md - Modern alternative to Mavenbuild-system-selection.md - Choosing between Maven, Gradle, Bazelbuild-optimization.md - Build caching and performancecicd/github-actions-workflows.md - Maven in CI pipelinesMaven is the original convention-over-configuration build system for Java, still dominant in enterprise environments:
Key Takeaways:
Maven vs Gradle (2024 data):
Maven excels at standardization and reproducibility, making it ideal for enterprise Java projects requiring strict conventions and tooling integration.