Maven Build System
Implements Apache Maven build configurations for Java and Kotlin projects — managing multi-module reactor builds, centralized dependency versioning with BOMs, plugin management, profile activation, and production-grade diagnostic workflows. When loaded, the model acts as a senior build engineer producing correct pom.xml files, resolving dependency conflicts, and diagnosing build issues using Maven's built-in diagnostics.
TL;DR Checklist
- Use a parent POM with
<dependencyManagement>and<pluginManagement>for all multi-module projects - Define a dedicated BOM module (packaging:
pom) to centralize third-party dependency versions - Add maven-enforcer-plugin rules enforcing Java version, Maven version, and duplicate detection
- Use
${project.version}instead of hardcoded version numbers in child POMs - Run
mvn help:effective-pomandmvn dependency:tree -Dincludes=groupId:artifactIdto diagnose issues - Set
<scope>test</scope>on all test-scoped dependencies; never leave runtime deps as test scope - Use
<exclusions>to resolve transitive dependency conflicts at the point of declaration
When to Use
Use this skill when:
- Setting up a new Maven project or multi-module reactor from scratch
- Resolving dependency version conflicts across modules using BOMs or
dependencyManagement - Configuring Maven profiles for environment-specific builds (dev, staging, production)
- Diagnosing build failures with
effective-pom,dependency:tree, and enforcer rules - Migrating a non-Maven project to Maven or consolidating multiple projects into a reactor
- Configuring plugin management to standardize compiler, test, and packaging behavior across modules
When NOT to Use
Avoid this skill for:
- Non-Java/Kotlin projects — use
gradle-build-systemfor Gradle-based builds instead - Simple single-file scripts — Maven's overhead is not justified for trivial build needs
- Projects already committed to Gradle with convention plugins and Kotlin DSL
- When only dependency resolution (not full build lifecycle) is needed — consider a dedicated tool like JBang or direct classpath management
Core Workflow
Define Reactor Structure — Create a root
pom.xmlwith<packaging>pom</packaging>, declare child modules via<modules>, and set the parentgroupId/artifactId/version. Every child module must inherit these through a<parent>reference with<relativePath>../pom.xml</relativePath>. Checkpoint: Runmvn help:evaluate -Dexpression=project.modules -q -DforceStdoutto verify all modules are discovered. No child POM should declare its owngroupIdorversionwhen inheriting from parent.Configure Dependency Management — In the parent POM, place all project dependency coordinates inside
<dependencyManagement>. For shared third-party libraries (SLF4J, JUnit, Jackson), create a separate BOM module withpompackaging that centralizes versions. Child modules reference dependencies without version numbers — Maven resolves them from the parent's management section. Checkpoint: Runmvn dependency:tree -Dincludes=com.fasterxml.jackson.corefrom the root. All modules must show exactly one Jackson version with no conflict warnings.Centralize Plugin Management — Declare plugin versions inside
<build><pluginManagement>in the parent POM. Apply plugins to individual modules by listing them under each module's<build><plugins>section without specifying versions (they inherit from pluginManagement). Checkpoint: Runmvn help:effective-pom -pl :my-serviceand verify thatmaven-compiler-pluginversion matches your declared value. Maven must not emit "Plugin management" warnings about unmanaged plugins.Enforce Build Policies — Add the maven-enforcer-plugin with at least three rules:
enforceJavaVersion,enforceMavenVersion, andbanDuplicatePomDependencies. Configure failure behavior to stop the build on violations. Checkpoint: Runmvn enforcer:enforcefrom the root in a clean checkout — it must pass. Then intentionally inject a duplicate dependency in one module and verify it fails with a clear error message naming the duplicate artifacts.Verify Reactor Build — Execute
mvn clean verifyfrom the reactor root. Review the output for version convergence warnings, plugin conflicts, or missing dependencies. The build exit code must be 0. Checkpoint: Confirm that all modules compile, all tests pass, and no module shows[WARNING] Found dependency conflict. Check that inter-module dependencies resolve correctly (e.g.,my-apidepends onmy-core,my-servicedepends onmy-api).Diagnostic Resolution — When builds fail or behave unexpectedly, use Maven's diagnostic commands:
mvn help:effective-pomto see the resolved POM after interpolation and inheritance,mvn dependency:tree -Dverbosefor full transitive resolution details, andmvn help:evaluate -Dexpression=propertyNameto inspect property values. Checkpoint: Every dependency conflict must have an explicit<exclusion>in the module that introduces it, accompanied by a comment explaining why.
Implementation Patterns / Reference Guide
Pattern 1: Parent POM with Dependency and Plugin Management
A parent POM defines shared configuration while child modules inherit and specialize. The reactor build processes all modules in declaration order, resolving inter-module dependencies automatically.
<!-- Root pom.xml — Maven multi-module parent (Maven 3.9+) -->
<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>
<groupId>com.example</groupId>
<artifactId>my-platform</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>My Platform Parent</name>
<description>Parent POM for the My Platform multi-module project</description>
<modules>
<module>my-core</module>
<module>my-api</module>
<module>my-service</module>
<module>my-web</module>
<module>my-bom</module>
</modules>
<!-- Centralized dependency versions -->
<dependencyManagement>
<dependencies>
<!-- Internal modules — no version needed in children (inherit from parent) -->
<dependency>
<groupId>com.example</groupId>
<artifactId>my-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>my-api</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Third-party dependencies with centralized versions -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.16</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.7</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.18.2</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.11.4</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- Plugin versions centralized here -->
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>21</release>
<parameters>true</parameters>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.5.0</version>
</plugin>
</plugins>
</pluginManagement>
</build>
<!-- Properties for version management -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>21</maven.compiler.release>
</properties>
</project>
Pattern 2: Child Module POM with Dependencies
Child modules inherit everything from the parent. They only declare their own specific dependencies and plugin executions, never specifying versions for managed dependencies.
<!-- my-service/pom.xml — A child module -->
<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>
<!-- Inherits groupId, version, and all management sections from parent -->
<parent>
<groupId>com.example</groupId>
<artifactId>my-platform</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>my-service</artifactId>
<name>My Service Module</name>
<description>Core business logic for the service layer</description>
<!-- Dependencies: version omitted — resolved from parent's dependencyManagement -->
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>my-api</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Exclude Log4j-over-SLF4j — causes classloader conflicts in Spring Boot -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>2.24.3</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.3.232</version>
<scope>test</scope>
</dependency>
</dependencies>
<!-- Apply enforcer plugin execution to this module -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<executions>
<execution>
<id>enforce-rules</id>
<goals><goal>enforce</goal></goals>
<configuration>
<rules>
<requireUpperBoundDeps/>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Pattern 3: BOM (Bill of Materials) Module for Third-Party Versions
A dedicated BOM module allows the organization to manage third-party dependency versions in a single place. Child projects import it with <scope>import</scope> in their own dependencyManagement section. This is the Maven equivalent of Gradle's platform BOM.
<!-- my-bom/pom.xml — Centralized third-party version management -->
<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>
<parent>
<groupId>com.example</groupId>
<artifactId>my-platform</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>my-bom</artifactId>
<packaging>pom</packaging>
<name>My Platform BOM — Third-Party Dependency Versions</name>
<description>Import this BOM to use centralized third-party versions. Do not depend on this module as a library.</description>
<!-- This section becomes the BOM content when imported with scope=import -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.github.classgraph</groupId>
<artifactId>classgraph</artifactId>
<version>4.8.179</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.3.1-jre</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.17.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.18.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
<scope>provided</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
Child projects import the BOM:
<dependencyManagement>
<dependencies>
<!-- Import the organization's BOM — scope=import makes it act as dependencyManagement -->
<dependency>
<groupId>com.example</groupId>
<artifactId>my-bom</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Pattern 4: maven-enforcer-plugin with Production-Grade Rules
The enforcer plugin prevents dangerous or inconsistent builds by enforcing policies at build time. Configure it in the parent POM's <pluginManagement> with execution bindings that run during the verify phase.
<!-- Enforcer configuration — parent POM's pluginManagement -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<executions>
<execution>
<id>enforce-java-version</id>
<goals><goal>enforce</goal></goals>
<configuration>
<rules>
<requireJavaVersion>
<version>[17,)</version>
<message>Java 17 or higher is required for this project.</message>
</requireJavaVersion>
<requireMavenVersion>
<version>[3.9.0,)</version>
<message>Maven 3.9+ is required for modern dependency resolution.</message>
</requireMavenVersion>
</rules>
</configuration>
</execution>
<execution>
<id>enforce-dependency-convergence</id>
<goals><goal>enforce</goal></goals>
<configuration>
<rules>
<requireUpperBoundDeps/>
</rules>
</configuration>
</execution>
</executions>
</plugin>
Pattern 5: Maven Profiles for Environment-Specific Builds
Profiles activate conditionally based on OS, properties, or default settings. Use profiles to manage environment-specific configurations such as repository URLs, profiled code paths, and build flags.
<!-- Parent POM — Profile definitions -->
<profiles>
<!-- Development profile: activated by default for local work -->
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<environment.type>development</environment.type>
<!-- Enable debug logging in dev builds -->
<log.level>DEBUG</log.level>
<!-- Skip integration tests during rapid dev cycles -->
<skipITs>true</skipITs>
</properties>
</profile>
<!-- Production profile: activated via -Pprod or CI environment variable -->
<profile>
<id>prod</id>
<activation>
<property><name>env</name><value>prod</value></property>
</activation>
<properties>
<environment.type>production</environment.type>
<log.level>WARN</log.level>
</properties>
<!-- Can override dependencies for production (e.g., different DB driver) -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<Environment>${environment.type}</Environment>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<!-- Profile for running integration tests with Maven Toolchains -->
<profile>
<id>integration-test</id>
<activation>
<property><name>skipITs</name><value>false</value></property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.5.2</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
Maven Diagnostic Commands Reference
When builds fail or behave unexpectedly, these commands diagnose the root cause:
| Command | Purpose |
|---|---|
mvn help:effective-pom |
Show the fully resolved POM after inheritance, interpolation, and profile activation |
mvn help:effective-pom -pl :my-service |
Effective POM for a specific module |
mvn dependency:tree |
Display the full dependency tree with transitive dependencies |
mvn dependency:tree -Dincludes=com.fasterxml.jackson.core |
Filter the dependency tree to specific artifacts |
mvn dependency:tree -Dverbose |
Show why each dependency was selected (path length, conflicts) |
mvn help:evaluate -Dexpression=project.version -q -DforceStdout |
Print a single property value directly to stdout |
mvn enforcer:enforce |
Run enforcer rules and report violations |
mvn dependency:analyze |
Report used/declared and unused declared dependencies |
mvn -N help:effective-pom |
Show the effective POM of the reactor root (use -N for non-recursive) |
BAD vs. GOOD: Common Maven Mistakes
❌ Hardcoding versions in child POMs without dependencyManagement
<!-- ❌ BAD — Child declares version directly; easy to drift across modules -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.16</version> <!-- Hardcoded — if another module uses 2.0.9, conflict! -->
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version> <!-- Different version from my-api module → runtime failure -->
</dependency>
✅ Centralized versions in parent dependencyManagement
<!-- ✅ GOOD — Version declared once in parent; children omit version entirely -->
<!-- Parent pom.xml -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.16</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.18.2</version>
</dependency>
</dependencies>
</dependencyManagement>
<!-- Child pom.xml — no version needed -->
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
❌ Using <source> and <target> instead of <release>
<!-- ❌ BAD — source/target split compilation into two phases; does not set bootstrap classpath -->
<configuration>
<source>21</source>
<target>21</target>
</configuration>
✅ Using the unified release flag (Java 9+)
<!-- ✅ GOOD — Single flag, sets all three aspects correctly for modern Java -->
<configuration>
<release>21</release>
</configuration>
Constraints
MUST DO
- Always use a parent POM with
<dependencyManagement>and<pluginManagement>for multi-module projects - Define a BOM module (
pompackaging) for third-party dependency version alignment; child modules import it with<scope>import</scope> - Use
${project.version}in all child POMs instead of hardcoded version numbers to keep releases synchronized - Add maven-enforcer-plugin with at least
requireJavaVersion,requireMavenVersion, andrequireUpperBoundDepsrules - Set
<scope>test</scope>explicitly on all test-scoped dependencies — never rely on default scope (compile) for tests - Use
<exclusions>to resolve transitive dependency conflicts at the point of declaration, with a comment explaining why - Run
mvn clean verifyfrom the reactor root as the standard build command before committing changes
MUST NOT DO
- Hardcode dependency versions in child POMs when a parent
dependencyManagementsection exists — this creates silent version drift - Remove the
<relativePath>from<parent>declarations — Maven falls back to the repository, which is slower and may resolve the wrong version - Use
<source>and<target>instead of<release>for Java compilation — use the unifiedreleaseflag introduced in Java 9 - Declare modules that do not exist as directories — Maven will fail with "module directory does not exist"
- Override enforcer-plugin rules in child POMs to disable them for individual modules — this defeats build-time policy enforcement
- Use
<scope>system</scope>for dependencies — it breaks portability and CI builds; always use Maven Central or a private repository
Related Skills
| Skill | Purpose |
|---|---|
gradle-build-system |
Alternative build system using Gradle Kotlin DSL, version catalogs, and convention plugins for projects that prefer Gradle over Maven |
Live References
Authoritative documentation links for this skill's domain. The model follows markdown links at load time to resolve external references and inline content.