Skip to content
Back to skills

Maven Skill

ASecurity

Set up, harden, and debug Maven builds: a parent POM that owns every version, a BOM import instead of hand-picked versions, every plugin pinned, Enforcer rules that fail the build on version conflicts, unit and integration tests split across Surefire and Failsafe, a JaCoCo coverage gate, formatting, an SBOM, reproducible jars, and the Maven Wrapper. The example POM here was built with Maven 3.9.16 and JDK 21.

  • 3 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 1, 2026
ai-agentsgojavabashreactspringdockerapisecurity

Works with

  • api

Security analysis

A100/100

Pro scans all 21 files and shows the line behind each finding

Scanned October 1, 2026

npx -y skills add sharmapuneet1510/awesome-prompts --skill skills --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Maven Skill?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Maven Skill
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/sharmapuneet1510-maven-skill/badge)](https://www.skillsdirectory.com/skills/sharmapuneet1510-maven-skill)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: Maven Skill
version: 1.0
description: >
  Set up, harden, and debug Maven builds: a parent POM that owns every version,
  a BOM import instead of hand-picked versions, every plugin pinned, Enforcer
  rules that fail the build on version conflicts, unit and integration tests
  split across Surefire and Failsafe, a JaCoCo coverage gate, formatting, an
  SBOM, reproducible jars, and the Maven Wrapper. The example POM here was
  built with Maven 3.9.16 and JDK 21.
applies_to: [java, maven, spring-boot, build, ci]
tags: [maven, pom, bom, enforcer, surefire, failsafe, jacoco, spotless, cyclonedx, dependency-management]
---

# Maven Skill — v1.0

## Quick Card

> Read this card first. Load a section below only when the task needs it.

| | |
|---|---|
| **Use when** | Creating or fixing a Maven build, adding a module, resolving a dependency conflict, or wiring Maven into CI — `implementer:build`, `implementer:pipeline` |
| **Skip when** | Gradle project — the principles hold, the syntax does not |
| **Inputs** | JDK version, framework (Spring Boot or none), module list, artifact repository (Maven Central or a corporate mirror) |
| **Produces** | Parent `pom.xml`, module POMs, `.mvn/maven.config`, Maven Wrapper, CI command |
| **Steps** | 1. Parent POM owns versions (§2) → 2. Import the framework BOM → 3. Pin every plugin (§3) → 4. Enforcer rules (§4) → 5. Tests + coverage (§5) → 6. Wrapper + CI (§6) → 7. Diagnose conflicts with the tree (§7) |
| **Done when** | `./mvnw verify` passes from a clean checkout; Enforcer passes; no version appears in a module POM |
| **Senior defaults** | Versions only in the parent's `dependencyManagement` / `pluginManagement` · import a BOM, never hand-pick Spring or Jackson versions · pin **every** plugin, including clean/install/deploy/site · `dependencyConvergence` fails the build instead of "nearest wins" guessing · never version ranges · `project.build.outputTimestamp` for reproducible jars · credentials in `settings.xml`, never in a POM |
| **Load on demand** | §1 layout · §2 parent POM · §3 plugins · §4 enforcer · §5 tests and coverage · §6 wrapper and CI · §7 conflicts · §8 repositories and security · §9 release |
| **Run report** | `html_report_skill` — adds: Plugin versions · Enforcer results · Dependency changes |
| **Pairs with** | `java_advanced_skill`, `spring_advanced_skill`, `test_skill`, `project_setup_skill`, `security_audit_skill` (dependency CVEs) |

---

## 1. Layout

```text
orders/
├── pom.xml                  ← parent: packaging pom, versions, plugins, rules
├── mvnw, mvnw.cmd           ← wrapper — commit these
├── .mvn/
│   ├── maven.config         ← default flags for every invocation
│   └── wrapper/maven-wrapper.properties
├── core/pom.xml             ← domain code, no framework
└── app/pom.xml              ← depends on core; Spring Boot lives here
```

One reactor, one version for all modules. A module POM names its parent, its
`artifactId`, and dependencies **without versions**.

## 2. The Parent POM

This is the exact POM that built the example project: two modules, three unit
tests, one integration test, coverage gate met, Enforcer passing, SBOM written.
Versions were the newest stable releases on Maven Central on 2026-09-28 —
check again before copying (`./mvnw versions:display-plugin-updates`).

```xml
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.example</groupId>
  <artifactId>orders-parent</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <packaging>pom</packaging>

  <modules>
    <module>core</module>
    <module>app</module>
  </modules>

  <properties>
    <java.version>21</java.version>
    <maven.compiler.release>${java.version}</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <!-- reproducible builds: fixed timestamp in every jar -->
    <project.build.outputTimestamp>2026-09-28T00:00:00Z</project.build.outputTimestamp>
    <spring-boot.version>4.1.1</spring-boot.version>
    <jacoco.minimum.line.coverage>0.80</jacoco.minimum.line.coverage>
  </properties>

  <!-- Versions live here and only here. Modules declare dependencies without versions. -->
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>${spring-boot.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <dependency>
        <groupId>com.example</groupId>
        <artifactId>orders-core</artifactId>
        <version>${project.version}</version>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <build>
    <pluginManagement>
      <plugins>
        <!-- Default-lifecycle plugins: pin them too, or requirePluginVersions fails
             and builds silently change when Maven itself is upgraded -->
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.5.0</version>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.5.0</version>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-jar-plugin</artifactId>
          <version>3.5.1</version>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-install-plugin</artifactId>
          <version>3.2.0</version>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>3.2.0</version>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-site-plugin</artifactId>
          <version>3.22.0</version>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.16.0</version>
          <configuration>
            <parameters>true</parameters>
            <compilerArgs>
              <arg>-Xlint:all</arg>
            </compilerArgs>
          </configuration>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>3.6.0</version>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-failsafe-plugin</artifactId>
          <version>3.6.0</version>
        </plugin>
        <plugin>
          <groupId>org.jacoco</groupId>
          <artifactId>jacoco-maven-plugin</artifactId>
          <version>0.8.15</version>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-enforcer-plugin</artifactId>
          <version>3.6.3</version>
        </plugin>
        <plugin>
          <groupId>com.diffplug.spotless</groupId>
          <artifactId>spotless-maven-plugin</artifactId>
          <version>3.10.3</version>
        </plugin>
        <plugin>
          <groupId>org.codehaus.mojo</groupId>
          <artifactId>versions-maven-plugin</artifactId>
          <version>2.22.0</version>
        </plugin>
        <plugin>
          <groupId>org.cyclonedx</groupId>
          <artifactId>cyclonedx-maven-plugin</artifactId>
          <version>2.9.3</version>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-dependency-plugin</artifactId>
          <version>3.11.0</version>
        </plugin>
      </plugins>
    </pluginManagement>

    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-enforcer-plugin</artifactId>
        <executions>
          <execution>
            <id>enforce</id>
            <goals><goal>enforce</goal></goals>
            <configuration>
              <rules>
                <requireMavenVersion><version>[3.9.0,)</version></requireMavenVersion>
                <requireJavaVersion><version>[${java.version},)</version></requireJavaVersion>
                <dependencyConvergence/>
                <banDuplicatePomDependencyVersions/>
                <requirePluginVersions/>
              </rules>
            </configuration>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>com.diffplug.spotless</groupId>
        <artifactId>spotless-maven-plugin</artifactId>
        <configuration>
          <java>
            <googleJavaFormat/>
            <removeUnusedImports/>
          </java>
        </configuration>
        <executions>
          <execution>
            <goals><goal>check</goal></goals>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-failsafe-plugin</artifactId>
        <executions>
          <execution>
            <goals>
              <goal>integration-test</goal>
              <goal>verify</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.jacoco</groupId>
        <artifactId>jacoco-maven-plugin</artifactId>
        <executions>
          <execution>
            <id>prepare-agent</id>
            <goals><goal>prepare-agent</goal></goals>
          </execution>
          <execution>
            <id>report</id>
            <goals><goal>report</goal></goals>
          </execution>
          <execution>
            <id>check</id>
            <goals><goal>check</goal></goals>
            <configuration>
              <rules>
                <rule>
                  <element>BUNDLE</element>
                  <limits>
                    <limit>
                      <counter>LINE</counter>
                      <value>COVEREDRATIO</value>
                      <minimum>${jacoco.minimum.line.coverage}</minimum>
                    </limit>
                  </limits>
                </rule>
              </rules>
            </configuration>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.cyclonedx</groupId>
        <artifactId>cyclonedx-maven-plugin</artifactId>
        <executions>
          <execution>
            <phase>package</phase>
            <goals><goal>makeAggregateBom</goal></goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>
```

A module then needs nothing but coordinates:

```xml
<parent>
  <groupId>com.example</groupId>
  <artifactId>orders-parent</artifactId>
  <version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>orders-app</artifactId>

<dependencies>
  <dependency>
    <groupId>com.example</groupId>
    <artifactId>orders-core</artifactId>
  </dependency>
  <dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <scope>test</scope>
  </dependency>
</dependencies>
```

**BOM import vs. `spring-boot-starter-parent`.** Inheriting the Boot parent is
fine for a single-module app. For a multi-module build with its own parent,
import `spring-boot-dependencies` as above — a project has only one parent,
and it should be yours.

## 3. Plugins — Pin Every One

`requirePluginVersions` fails the build until every plugin has a version,
including the ones Maven binds by default. On the example it failed first on
`maven-clean-plugin`, `maven-install-plugin`, `maven-site-plugin` and
`maven-deploy-plugin` — which were running whatever Maven 3.9.16 ships. Unpinned,
they change the day someone upgrades Maven.

| Plugin | Job |
|---|---|
| `maven-compiler-plugin` | `maven.compiler.release` (not `source`/`target` — `release` also checks the API), `-parameters`, `-Xlint:all` |
| `maven-surefire-plugin` | Unit tests: `*Test.java` in `test` |
| `maven-failsafe-plugin` | Integration tests: `*IT.java` in `integration-test`, failures reported in `verify` so cleanup still runs |
| `jacoco-maven-plugin` | Coverage; `check` fails below the floor |
| `maven-enforcer-plugin` | Build rules, §4 |
| `spotless-maven-plugin` | Formatting: `check` in CI, `apply` locally |
| `cyclonedx-maven-plugin` | SBOM (`target/bom.json`) for supply-chain scanning |
| `versions-maven-plugin` | `display-dependency-updates`, `display-plugin-updates` |

## 4. Enforcer Rules

| Rule | Catches |
|---|---|
| `requireMavenVersion` / `requireJavaVersion` | "Works on my machine" toolchains |
| `dependencyConvergence` | Two versions of one artifact in the tree, where Maven would silently pick the nearest |
| `banDuplicatePomDependencyVersions` | The same dependency declared twice in one POM |
| `requirePluginVersions` | Any unpinned plugin, including default-lifecycle ones |

Verified on the example: adding `org.reflections:reflections:0.10.2` (which
brings `javassist` 3.28.0-GA) next to a direct `javassist` 3.30.2-GA failed
the build:

```text
Rule 2: org.apache.maven.enforcer.rules.dependency.DependencyConvergence failed with message:
Dependency convergence error for org.javassist:javassist:jar:3.28.0-GA. Paths to dependency are:
    +-org.javassist:javassist:jar:3.28.0-GA:compile
  +-org.javassist:javassist:jar:3.30.2-GA:compile
```

Fixed by managing the version once in the parent — the rule passed:

```xml
<dependencyManagement>
  <dependencies>
    <!-- reflections pulls 3.28.0-GA; one version for the whole build -->
    <dependency>
      <groupId>org.javassist</groupId>
      <artifactId>javassist</artifactId>
      <version>3.30.2-GA</version>
    </dependency>
  </dependencies>
</dependencyManagement>
```

## 5. Tests and Coverage

- **The coverage floor is the project's, not this file's.** Set `jacoco.minimum.line.coverage` to the floor in `docs/project-setup/rules.md` (`project_setup_skill` §5 suggests 90%). The example's 0.80 is what that example project used.
- **Unit tests** end in `Test` → Surefire, `test` phase. **Integration tests** end in `IT` → Failsafe, `integration-test` + `verify`.
- `jacoco:prepare-agent` sets `argLine`; Surefire **and** Failsafe both use it, so coverage from integration tests counts. On the example, the `app` module has only an `IT` and still met its coverage gate.
- **If you set your own `argLine`, write `<argLine>@{argLine} -Xmx1g</argLine>`.** Verified on the example: a plain `<argLine>-Xmx512m</argLine>` dropped the JaCoCo agent, JaCoCo logged `Skipping JaCoCo execution due to missing execution data file`, and the build still ended in `BUILD SUCCESS` — the coverage gate never ran. With `@{argLine}` the gate ran and passed.
- A module with no tests at all prints `Skipping JaCoCo execution due to missing execution data file` and passes — the gate cannot see what never ran. Every module with code needs tests.
- Testcontainers-based tests belong in `*IT` classes: they need Docker and are slow.

## 6. Wrapper and CI

```bash
mvn -N wrapper:wrapper -Dmaven=3.9.16   # writes mvnw, mvnw.cmd, .mvn/wrapper/ — commit them
```

`.mvn/maven.config` — flags every invocation gets:

```text
-B
-ntp
```

(`-B` batch mode, `-ntp` no transfer progress — keeps CI logs readable.)

CI runs one command:

```bash
./mvnw verify
```

`verify`, not `install`: nothing needs to land in `~/.m2`. Cache `~/.m2/repository`
keyed on the hash of all `pom.xml` files. Use `-T 1C` for parallel module builds
once the build is known to be thread-safe.

## 7. Dependency Conflicts

```bash
./mvnw dependency:tree -Dincludes=org.javassist        # who brings it in
./mvnw dependency:tree -Dverbose                       # shows omitted-for-conflict versions
./mvnw dependency:analyze                              # used-undeclared and declared-unused
./mvnw versions:display-dependency-updates             # what is out of date
```

| Symptom | Cause | Fix |
|---|---|---|
| `NoSuchMethodError` / `ClassNotFoundException` at runtime | Two versions in the tree; the older one won | Pin in `dependencyManagement`; `dependencyConvergence` prevents recurrence |
| Enforcer convergence failure after adding a library | Library needs a different version of a shared dependency | Manage the version in the parent; exclude only when the library works with yours |
| `dependency:analyze` lists used-undeclared | Code compiles against a transitive dependency | Declare it — the transitive can disappear on any upgrade |
| Build differs between machines | Unpinned plugin, `SNAPSHOT` dependency, or version range | Pin; release snapshots; never `[1.0,)` |

`<exclusions>` is a last resort — document why on the exclusion.

## 8. Repositories and Security

- **Credentials live in `~/.m2/settings.xml`** (`<servers>`), referenced by `id` — never in a POM, never in the repository. In CI, generate `settings.xml` from secrets.
- **Corporate mirror**: one `<mirror>` with `<mirrorOf>*</mirrorOf>` in `settings.xml`, so every artifact goes through the proxy.
- **No `http://` repositories** — Maven 3.8.1+ blocks them by default for a reason.
- **Scan dependencies**: feed `target/bom.json` to a scanner, or run `org.owasp:dependency-check-maven` in a scheduled job — it is slow on the first run.
- **Scopes**: `provided` for container-supplied APIs, `test` for test-only, `runtime` for drivers; `optional` only in libraries.

## 9. Releasing

- Versions: `1.4.0-SNAPSHOT` during development, `1.4.0` for the release.
  `./mvnw versions:set -DnewVersion=1.4.0 -DgenerateBackupPoms=false` sets every module at once.
- Tag, build from the tag, `./mvnw deploy` with release credentials.
- `project.build.outputTimestamp` makes jars byte-identical across rebuilds —
  set it to the release commit's timestamp when releasing.

## 10. Checklist

✅ Maven Wrapper committed; CI runs `./mvnw verify`
✅ All versions in the parent's `dependencyManagement` / `pluginManagement`; none in modules
✅ Framework BOM imported
✅ Every plugin pinned, including clean, resources, jar, install, deploy, site
✅ Enforcer: Maven and Java versions, dependency convergence, duplicate declarations, plugin versions
✅ Unit tests in Surefire, `*IT` in Failsafe; coverage gate on both
✅ Formatter check in CI
✅ SBOM generated; dependencies scanned
✅ No credentials, `http://` repositories, version ranges, or snapshots in a release
✅ `project.build.outputTimestamp` set

Files in this skill

  • README.md13.6 KB
  • adr_skill.md9.6 KB
  • agent_skill_design_skill.md5.3 KB
  • ansible_skill.md14.2 KB
  • apache_camel_skill.md16.9 KB
  • apache_pulsar_skill.md18.4 KB
  • ba_create_skill.md20.5 KB
  • backend_skill.md23.8 KB
  • code_documentation_skill.md14.6 KB
  • code_formatting_skill.md12.4 KB
  • code_health_skill.md10.8 KB
  • code_review_skill.md37.7 KB
  • context_builder_skill.md13.3 KB
  • current_tech_spec_skill.md7.4 KB
  • database_skill.md23.1 KB
  • debugging_skill.md4.4 KB
  • error_handling_skill.md19.7 KB
  • frontend_skill.md24.9 KB
  • html_report_skill.md8.3 KB
  • java_advanced_skill.md16.3 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…