Gradle Publishing, Maven Publish, Ivy Publish, Signing, Metadata, and Repository Promotion: Configuration, Design Choices, and Tradeoffs
Choose Maven or Ivy metadata, rich Gradle metadata, signing, repository topology, and staging/promotion controls by tracing the consumer contract, trust boundary, reproducibility requirement, and operational ownership each choice changes.
Learning objectives
- Choose Maven or Ivy publishing formats according to consumer ecosystems rather than personal DSL preference.
- Decide when rich Gradle metadata is safe, when Maven compatibility constrains the model, and how version mapping changes POM evidence.
- Separate direct publication from repository-managed staging/promotion.
- Choose credential/signing placement and local-versus-authoritative repository policy.
- Justify each choice using observable repository and consumer behavior.
1. Maven versus Ivy is a consumer-contract decision
Do not start with “which Gradle plugin do I like?” Start with the consumers and repository manager. Maven-format repositories are the broad JVM interoperability default. Ivy remains useful where an organization deliberately operates Ivy metadata/layouts or needs Ivy-specific descriptor semantics. Applying both in one build can be useful for learning, but publishing two authoritative formats for the same product creates more metadata to govern.
2. Rich GMM versus Maven-compatible surface
Gradle consumers can use GMM to select variants with attributes/capabilities that a Maven POM cannot model. Maven consumers see the POM only. A library that exposes a plain Java API/runtime split usually maps cleanly. A library with custom variants, rich constraints, or Gradle-specific capabilities may not.
Publication warnings are therefore design feedback. Before suppressing one, compare the resolved graph from a clean Gradle consumer and a clean Maven consumer. If the graphs or available artifacts differ in a way that changes runtime behavior, the problem is not cosmetic.
3. Declared versus resolved dependency versions in POMs
By default, Maven Publish writes the versions declared by the build
model. Gradle also supports versionMapping to publish
resolved versions. That can be appropriate when a release is tested
against locked/resolved versions or when rich Gradle constraints
would map poorly to Maven.
publishing {
publications {
named<MavenPublication>("mavenJava") {
versionMapping {
usage("java-api") {
fromResolutionOf("runtimeClasspath")
}
usage("java-runtime") {
fromResolutionResult()
}
}
}
}
}
This is not universally “more reproducible.” It changes the public dependency contract. Review the generated POM diff and consumer graph before adopting it, especially for libraries whose API dependencies intentionally allow a range of compatible versions.
4. Direct-to-release versus stage-then-promote
Direct publication minimizes moving parts but gives the build job immediate authority over the release repository. Staging adds a state boundary: upload candidate bytes once, run verification against those exact bytes, then promote them without rebuilding. For commercial/internal repositories, that separation usually makes audit and rollback reasoning clearer.
| Model | Advantage | Risk / cost | Evidence to require |
|---|---|---|---|
| Direct publish | Simple and fast | Publish job can create/replace authoritative identity immediately | Exact coordinate, permission check, repository immutability, checksum/signature evidence |
| Staging + promotion | Verify exact candidate before release visibility | Needs repository-manager workflow and retention policy | Staging digest = promoted digest; approval/audit record |
| Build again during release | Operationally familiar in some pipelines | Second build may produce different bytes/metadata | Avoid as promotion strategy; if unavoidable, treat as a new artifact and re-verify |
6. Credentials and signing are separate authorization layers
Repository credentials answer “may this build write here?” A signing key answers “which cryptographic identity produced this signature?” The same CI job may possess both, but that does not make them the same secret. Least privilege often means a snapshot job can upload snapshots without a release signing key, while a protected release job receives both narrowly scoped repository write permission and signing capability.
Use repository-name-derived Gradle properties or provider-backed environment inputs. Never echo them. A Configuration Cache can serialize values used during configuration, so secret-handling policy must also consider cache storage and access controls.
7. Snapshots, releases, and mutable identity
A -SNAPSHOT version communicates changing development
state in Maven conventions. A non-snapshot release should be
immutable. Gradle can route repositories conditionally based on the
project version, but routing is not an immutability guarantee; the
destination repository must enforce the rule.
publishing {
repositories {
maven {
name = "target"
val releasesRepo = layout.buildDirectory.dir("repositories/releases")
val snapshotsRepo = layout.buildDirectory.dir("repositories/snapshots")
url = uri(if (version.toString().endsWith("SNAPSHOT")) snapshotsRepo else releasesRepo)
}
}
}
This snippet is safe only as a routing example. Production URLs, credentials, retention, and overwrite policy belong to repository governance.
8. Decision table
| Situation | Recommended publishing approach | Why |
|---|---|---|
| Public/general JVM library | Maven publication + GMM; validate Maven consumer | Broad interoperability while preserving richer Gradle metadata |
| Internal Gradle-only ecosystem with custom variants | Maven or Ivy repository + GMM, with explicit compatibility boundary | Gradle consumers can use rich variants; non-Gradle limitations are documented |
| Legacy Ivy estate | Ivy publication, optionally GMM for Gradle consumers | Matches existing repository/consumer metadata model |
| Release requiring approval/security gates | Publish candidate once to staging, then repository-managed promotion | Build once; verify/promote identical bytes |
| Local integration experiment | Disposable project-relative Maven repository | Inspectable and deletable; avoids normal Maven Local state |
| Developer convenience only | Optional Maven Local diagnostic | Useful bridge, but too mutable for authoritative release identity |
9. Worked scenario: one Gradle service team, two consumer ecosystems
A platform team owns a Java library consumed by 40 Gradle builds and 12 Maven builds. The Gradle consumers benefit from rich variants, but the Maven consumers must still receive a correct dependency graph. The team chooses Maven repository layout, publishes GMM alongside the POM, runs a Gradle consumer verification and Maven consumer verification against staging, signs the staged publication, and promotes the same coordinate tree. It does not create a Gradle-only release repository plus a separately rebuilt Maven artifact.
10. What this chapter does not configure
Gradle build logic can define publications, tasks, repository URLs, and credential interfaces. It does not administer Nexus/Artifactory retention, Maven Central namespaces, CI protected environments, organizational key custody, legal release approval, or vulnerability policy. Those systems consume the publication contract defined here.
Knowledge check
When is versionMapping a meaningful design change
rather than formatting?
When it changes which dependency versions Maven consumers see, especially if Gradle resolution/locking selected versions different from declared ones.
Why not publish separately rebuilt Gradle and Maven “release artifacts”?
They can have the same coordinate but different bytes, defeating immutable identity and making consumer-specific provenance hard to prove.
Does routing -SNAPSHOT and release versions to
different URLs enforce immutability?
No. Routing chooses a destination; repository policy must enforce overwrite/immutability rules.
What is the strongest reason to keep Maven consumer verification even when most consumers use Gradle?
Maven reads the POM, not GMM. A Gradle-only verification can miss lossy metadata mapping that changes Maven behavior.
Should a snapshot uploader automatically have access to the production signing key?
No. Repository write authorization and signing authority are separate privileges and should be scoped to the workflow that needs them.
Official references and version notes
- Gradle 9.7.1 release notes — pinned Gradle baseline for this chapter.
- Publishing a project as a module — publication = artifacts + metadata, repository target, publish task, checksums, and signatures when configured.
-
Maven Publish Plugin
—
MavenPublication, generated POM, repository tasks, Maven Local, version mapping, snapshot/release routing. -
Ivy Publish Plugin
—
IvyPublication, generatedivy.xml, Ivy repository layout and tasks. - Gradle Module Metadata — variant-aware metadata, POM/Ivy mapping, publication warnings, validation, reproducibility.
- Signing Plugin — OpenPGP signatures, in-memory keys, GPG command integration, signing publications.
- Supported repository protocols and credentials — file/HTTP(S) transports, externalized credentials, repository-name-derived properties.
- Metadata formats — Gradle Module Metadata, Maven POM, Ivy descriptors and consumer behavior.
- Apache Maven releases history — Maven 3.9.16 is the current GA baseline used for the clean Maven consumer lane.
- Maven Compiler Plugin — pinned 3.15.0 compiler plugin for the Maven consumer fixture.
Version-sensitive behavior was rechecked against current Gradle and
Apache Maven primary documentation on 2026-08-24. Mandatory
publication uses only repository-owned files plus disposable
file: repositories under the lab workspace. Maven
Local, real credentials, real release repositories, hosted signing,
Maven Central, Nexus/Artifactory administration, and CI-specific
secret stores are not mandatory.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.