Production-ready parent POM for Java 25 + Maven 3.9+ + Docker CI/CD. Provides opinionated, SOTA-2026 defaults for build, quality, security, packaging, and release. Inherit it to get a fully wired Java project in 10 lines of XML.
# Simple standalone project
mvn archetype:generate \
-DarchetypeGroupId=fr.ebruno.maven.archetypes \
-DarchetypeArtifactId=maven-archetype-simple \
-DgroupId=com.example \
-DartifactId=my-app \
-Dversion=1.0.0-SNAPSHOT
# Project inheriting this parent POM
mvn archetype:generate \
-DarchetypeGroupId=fr.ebruno.maven.archetypes \
-DarchetypeArtifactId=maven-archetype-withparent \
-DgroupId=com.example \
-DartifactId=my-app \
-Dversion=1.0.0-SNAPSHOT<project>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>fr.ebruno.maven.poms</groupId>
<artifactId>maven-parentpom</artifactId>
<version>0.1.20</version>
</parent>
<groupId>com.example</groupId>
<artifactId>my-app</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<github.account>your-org</github.account>
<!-- For applications: -->
<app.image>libs</app.image>
<app.main.class>com.example.MyApp</app.main.class>
</properties>
<dependencies>
<!-- Versions are managed — just declare groupId:artifactId -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>That's it. You get: enforced Java/Maven versions, dependency convergence, JaCoCo coverage, SonarQube, Spotless formatting, GPG signing, SBOM, OWASP scanning, and release tooling — all pre-configured.
This repository follows a trunk-based development model:
mainis the only long-lived branch (always deployable, always green)- Feature branches are short-lived (max 2 days) and merge via squash PR
- Releases are tags (
vX.Y.Z) onmain— no release branches
| Prefix | Purpose | Example |
|---|---|---|
feat/ |
New feature | feat/revision-property |
fix/ |
Bug fix | fix/enforcer-rule |
refactor/ |
Restructure | refactor/release-workflow |
ci/ |
CI/CD changes | ci/sota-2026-actions |
chore/ |
Deps, config | chore/bump-wrapper |
docs/ |
Documentation | docs/release-process |
Every PR and push to main triggers:
- Build + Test (Java 25, Maven 3.9.11, Temurin)
- SonarQube analysis
- CodeQL security analysis
- Dependency Review (license + vulnerability)
- SNAPSHOT deploy to GitHub Packages (on
mainpush)
All versions are managed in <dependencyManagement>. Declare dependencies in your child without <version>.
| Library | Version | Artifacts |
|---|---|---|
| JUnit | 6.1.3 | junit-bom (import), junit-jupiter, junit-platform-* |
| AssertJ | 3.27.7 | assertj-core |
| Mockito | 5.24.0 | mockito-core, mockito-junit-jupiter |
| Hamcrest | 3.0 | hamcrest, hamcrest-library |
| JSONassert | 1.5.3 | jsonassert |
| Awaitility | 4.3.0 | awaitility |
| REST Assured | 6.0.1 | rest-assured |
| Testcontainers | 2.0.5 | testcontainers-bom (import), junit-jupiter, postgresql (1.21.4) |
| Arquillian | 1.10.2.Final | arquillian-bom (import), arquillian-weld-embedded (4.0.0.Final) |
| Arquillian Cube | 2.0.0.Final | arquillian-cube-bom (import) |
| Library | Version | Artifacts |
|---|---|---|
| Jakarta Persistence | 3.2.0 | jakarta.persistence-api |
| EclipseLink | 5.0.2 | eclipselink |
| Hibernate ORM | 7.4.10.Final | hibernate-platform (import), hibernate-c3p0 |
| C3P0 | 0.14.2 | c3p0 |
| Library | Version | Artifacts |
|---|---|---|
| H2 | 2.5.250 | h2 |
| PostgreSQL | 42.7.13 | postgresql |
| MySQL Connector/J | 26.7.0 | mysql-connector-j |
| Library | Version | Artifacts |
|---|---|---|
| Jackson | 2.22.3 | jackson-bom (import), jackson-jakarta-rs-json-provider |
| JAXB | 4.0.5 / 4.0.9 | jakarta.xml.bind-api, jaxb-impl |
| Library | Version | Artifacts |
|---|---|---|
| JAX-RS | 4.0.0 | jakarta.ws.rs-api |
| Jersey | 4.0.2 | jersey-bom (import), jersey-client |
| Tyrus (WebSocket) | 2.2.2 | tyrus-server, tyrus-container-grizzly-server, tyrus-standalone-client |
| JWT (jjwt) | 0.13.0 | jjwt-api, jjwt-impl, jjwt-jackson |
| Library | Version | Artifacts |
|---|---|---|
| Jakarta EE API | 11.0.0 | jakarta.jakartaee-api, jakarta.jakartaee-web-api (provided) |
| EL | 4.0.2 | jakarta.el |
| Bean Validation | 3.0.0 | hibernate-validator (9.1.4.Final) |
| PrimeFaces | 16.0.0 | primefaces (jakarta classifier), all-themes (1.1.0) |
| Payara | 6.2023.11 | (Arquillian managed) |
| Library | Version | Artifacts |
|---|---|---|
| SLF4J | 2.0.20 | slf4j-bom (import) |
| Logback | 1.6.4 | logback-classic |
| Library | Version | Artifacts |
|---|---|---|
| Lombok | 1.18.48 | lombok (provided) |
| Eclipse Collections | 13.0.0 | eclipse-collections |
| Throwing-Function | 1.6.1 | throwing-function |
For application (non-library) modules, the app.image property selects the packaging mode. Setting it automatically activates the corresponding profile via Maven property activation.
app.image |
Profile | Build Output | Docker Target | Run Command |
|---|---|---|---|---|
libs |
libs |
Thin JAR + target/libs/*.jar |
finalLibs |
java -cp app.jar:libs/* ${app.main.class} |
jlink |
jlink |
Custom JPMS runtime (target/jlink/) |
finalJlink |
./jlink/bin/myapp |
native |
native |
Single native executable | finalNative |
./native-image/myapp |
| (empty) | — | Standard JAR (library) | — | — |
<properties>
<app.image>libs</app.image> <!-- libs | jlink | native -->
<app.main.class>com.example.MyApp</app.main.class> <!-- fully qualified main class -->
</properties><properties>
<!-- JPMS module name (defaults to ${project.groupId}.${project.artifactId}) -->
<app.module.name>com.example.myapp</app.module.name>
</properties>shadedjaris NOT part ofapp.image. Activate explicitly:mvn package -Pshadedjar- Static native binaries (alpine/musl): override in your module's POM:
<build> <plugins> <plugin> <groupId>org.graalvm.buildtools</groupId> <artifactId>native-maven-plugin</artifactId> <configuration> <buildArgs combine.children="append"> <arg>--static</arg> </buildArgs> </configuration> </plugin> </plugins> </build>
- jlink limitation:
jigsaw-maven-plugin1.1.3linkgoal only supportslauncher,module,output,modulePath,ignoreSigningInformation. ThemainClass,multiRelease,jlinkOptions,ignoreMissingDepselements are present for forward compatibility but currently no-ops.
| Profile | Activation | Purpose |
|---|---|---|
libs |
app.image=libs |
Copy runtime deps → target/libs/ |
jlink |
app.image=jlink |
Build JPMS jlink custom runtime |
native |
app.image=native |
GraalVM native image compilation |
shadedjar |
Manual: -Pshadedjar |
Fat JAR (maven-shade-plugin) |
gpgsigning |
Manual: -Pgpgsigning |
GPG signing (ed25519) |
mavencentral |
Manual: -Pmavencentral |
Central Portal deployment config |
javadoc |
Manual: -Pjavadoc |
Javadoc jar generation |
jacoco |
Automatic (file-activated) | Code coverage (JaCoCo) |
sonar |
Manual: -Psonar |
SonarQube analysis |
arq-payara-micro |
Manual | Arquillian + Payara Micro embedded |
arq-payara-managed |
Manual | Arquillian + Payara managed |
release |
Manual: -Prelease |
Release prep: enforcer + sources + javadoc + GPG sign + SBOM |
format |
Manual: -Pformat |
Spotless format/apply |
api-compat |
Manual: -Papi-compat |
RevAPI binary compatibility check |
it |
Automatic (file-activated on src/it) |
Maven Invoker integration tests |
| Rule | Purpose |
|---|---|
requireMavenVersion |
Maven ≥ 3.9.11 |
requireJavaVersion |
Java ≥ 25 |
dependencyConvergence |
All transitive deps resolve to same version |
banDuplicatePomDependencyVersions |
No duplicate declarations |
requireUpperBoundDeps |
No version conflicts in dependency tree |
banDynamicVersions |
No LATEST/RELEASE/ranges |
- Reports: HTML + XML (for SonarQube)
- Check: enforced minimum coverage (configurable per project)
- Excludes: test classes, generated code
- Plugin:
sonar-maven-plugin5.5.0.6356 - Quality gate:
sonar-quality-gate-maven-plugin1.3.0 - Connect via:
-Dsonar.host.url=... -Dsonar.token=...
- Formatter: Palantir Java Format 2.50.0
- Goals:
spotless:check(CI) /spotless:apply(dev) - Activate:
-Pformat
- Plugin:
revapi-maven-plugin0.28.1 - Detects binary incompatibilities between releases
- Activate:
-Papi-compat
| Tool | What it does | When |
|---|---|---|
| OWASP Dependency Check 13.0.0 | Scans dependencies against NVD (CVSS ≥ 9 fails) | CI: canary project |
| CycloneDX SBOM 2.9.3 | Generates bom.json + bom.xml |
CI: every build |
| GitHub CodeQL | SAST (static analysis) | CI: every push/PR |
| GitHub Dependency Review | Flags vulnerable deps in PRs | CI: every PR |
<dependency-check.version>13.0.0</dependency-check.version>
<!-- failBuildOnCVSS=9: only critical vulns fail the build -->The parent POM itself has packaging: pom (no direct dependencies), so OWASP runs against the canary integration test project (src/it/canary/) which has real dependencies (Jackson, JUnit).
CycloneDX generates both JSON and XML SBOMs on every build:
target/bom.json
target/bom.xml
Full standard: see
docs/RELEASING.md— trunk-based, tag-driven, two-tier publishing (reusable across allebproJava repos).
Releases are tag-driven and handled entirely by CI (release.yml). No maven-release-plugin, no manual mvn release:prepare.
- Tier 1 — GitHub Packages (internal): automatic on every release tag
- Tier 2 — Maven Central (public): opt-in via
workflow_dispatch+publish-central: true
git checkout main && git pull
git tag -a v0.1.20 -m "Release 0.1.20"
git push origin v0.1.20 # CI builds, signs, deploys, then bumps to 0.1.21-SNAPSHOTTrigger Actions → Release → Run workflow with version: 0.1.20 and publish-central: true.
| Secret | Purpose |
|---|---|
SIGN_KEY / SIGN_KEY_PASS |
GPG private key (ed25519) + passphrase |
GITHUBTOKEN |
PAT with packages:write + contents:write |
CENTRAL_PORTAL_USERNAME / CENTRAL_PORTAL_TOKEN |
Central Portal credentials (Tier 2 only) |
Tag format: v{major}.{minor}.{patch} — e.g., v0.1.20.
| Workflow | Trigger | Purpose |
|---|---|---|
ci-java.yml |
Push/PR to main |
Build, test, SonarQube |
security.yml |
Push/PR to main |
CodeQL, SBOM, OWASP canary, Dep Review |
release.yml |
Tag v* / workflow_dispatch |
Two-tier publish: GitHub Packages (always) + Central (opt-in) |
Self-hosted: self-hosted, linux, ebpro-org, x64
- Image:
sha-2d55143 - Pre-installed: Java 25 (Temurin), Maven 3.9.11, GPG, Docker
- Cached:
~/.m2/repositoryat/opt/cache/m2/repository
- Build:
ebpro/gh-actions-shared-java - Runner:
ebpro/quarkus-ci-runner
- JUnit 6 (Jupiter) —
junit-jupiteron classpath - Surefire 3.6.0 — runs
*Test.java - Failsafe 3.6.0 — runs
*IT.java(integration)
- Testcontainers 2.0.5 — PostgreSQL, etc.
- Arquillian — Payara Micro (embedded) or Payara Managed
- REST Assured 6.0.1 — HTTP API testing
The parent POM includes an invoker-based integration test (src/it/canary/) that:
- Creates a minimal child project inheriting this parent
- Compiles and runs it in an isolated Maven environment
- Verifies the parent POM works end-to-end
Activated automatically when src/it/ exists. Runs clean verify on the canary.
# Dependencies
mvn versions:display-dependency-updates \
-DrulesFile=https://bruno.univ-tln.fr/rules.xml
# Plugins
mvn versions:display-plugin-updates \
-DrulesFile=https://bruno.univ-tln.fr/rules.xml
# BOM
mvn versions:display-property-updates# Single property
mvn versions:set-property -Dproperty=jackson.version -DnewVersion=2.23.0
# All at once (careful!)
mvn versions:update-propertiesTag-driven — CI does the rest (build, sign, deploy, bump to next -SNAPSHOT).
See docs/RELEASING.md.
git checkout main && git pull
git tag -a v0.1.20 -m "Release 0.1.20"
git push origin v0.1.20| Decision | Rationale |
|---|---|
packaging: pom |
This is a parent, not a library. No code to compile. |
Property-based profile activation (app.image) |
Declarative: set a property, get the behavior. No -P flags needed in Dockerfiles. |
jlink + native as opt-in profiles |
Not every module is an app. Libraries stay lean. |
shadedjar separate from app.image |
Fat JAR is a deployment choice, not an identity choice. |
| OWASP on canary, not parent | Parent has zero dependencies (POM packaging). Canary has real deps. |
Enforcer dependencyConvergence |
Catches version conflicts early, before they cause runtime issues. |
Reproducible builds (outputTimestamp) |
Same inputs → same JAR bytes. Critical for supply chain security. |
| GPG ed25519 | Modern, fast, 256-bit security. Supersedes RSA-4096. |
| Central Portal (not OSSRH) | OSSRH is retired. Central Portal is the current publishing mechanism. |
maven-parentpom/
├── pom.xml # The parent POM (2300+ lines)
├── README.md # This file
├── docs/
│ └── RELEASING.md # Release standard (trunk-based, two-tier)
├── .github/
│ └── workflows/
│ ├── ci-java.yml # Build + test + deploy
│ ├── sonarqube.yml # SonarQube analysis
│ ├── security.yml # CodeQL + SBOM + OWASP + Dep Review
│ └── release.yml # Two-tier release: GitHub Packages + Central (opt-in)
├── src/
│ └── it/
│ ├── canary/ # Invoker IT: minimal child project
│ │ ├── pom.xml
│ │ ├── invoker.properties
│ │ └── src/main/java/...
│ └── settings.xml # Isolated Maven settings for invoker
└── .mvn/
└── wrapper/ # Maven Wrapper
| Resource | URL |
|---|---|
| Website | https://ebpro.github.io/maven-parentpom/ |
| Archetypes | https://github.com/ebpro/maven-archetypes |
| CI Runner | https://github.com/ebpro/quarkus-ci-runner |
| Shared Workflows | https://github.com/ebpro/gh-actions-shared-java |
| Versions Rules | https://bruno.univ-tln.fr/rules.xml |
| Central Portal | https://central.sonatype.com/ |
| NVD API | https://nvd.nist.gov/ |
| SonarQube | (org-internal) |
MIT © Emmanuel Bruno, Université de Toulon