|
| 1 | +# Releasing the Java line to Maven Central |
| 2 | + |
| 3 | +_Written from the **7.0.0** release (first publish from the consolidated |
| 4 | +reactor at `server/java/`, 2026-05-27)._ |
| 5 | + |
| 6 | +## Companion doc |
| 7 | + |
| 8 | +[`docs/RELEASING.md`](RELEASING.md) is TS / npm — different tooling, auth, |
| 9 | +and gotchas. Each language ecosystem gets its own guide per the policy |
| 10 | +documented there. Java tags are independent from TS (`v7.0.0` for Java; TS |
| 11 | +is on a separate `0.x` track). |
| 12 | + |
| 13 | +## What gets published |
| 14 | + |
| 15 | +The reactor (`server/java/pom.xml`) lists 14 publishable modules. As of |
| 16 | +7.0.0 the reactor explicitly **excludes** `archetype` and `examples` — they |
| 17 | +were aggregator entries but not consumer-facing dependencies, and keeping |
| 18 | +them in the reactor required `-pl` exclusions that didn't transitively |
| 19 | +cover `examples/*` sub-modules under Maven 3.6.3. They still exist as |
| 20 | +source directories (for scaffold + sample purposes) — just not built by |
| 21 | +the reactor and not deployed. |
| 22 | + |
| 23 | +| Module dir | Maven coords (`com.metaobjects:*`) | |
| 24 | +|---|---| |
| 25 | +| `metadata/` | `metaobjects-metadata` | |
| 26 | +| `codegen-base/` | `metaobjects-codegen-base` | |
| 27 | +| `codegen-mustache/` | `metaobjects-codegen-mustache` | |
| 28 | +| `codegen-spring/` | `metaobjects-codegen-spring` | |
| 29 | +| `codegen-kotlin/` | `metaobjects-codegen-kotlin` | |
| 30 | +| `codegen-plantuml/` | `metaobjects-codegen-plantuml` | |
| 31 | +| `render/` | `metaobjects-render` | |
| 32 | +| `metadata-ktx/` | `metaobjects-metadata-ktx` | |
| 33 | +| `om/` | `metaobjects-om` | |
| 34 | +| `omdb/` | `metaobjects-omdb` | |
| 35 | +| `omdb-ktx/` | `metaobjects-omdb-ktx` | |
| 36 | +| `dynamic/` | `metaobjects-dynamic-core` | |
| 37 | +| `core-spring/` | `metaobjects-core-spring` | |
| 38 | +| `maven-plugin/` | `metaobjects-maven-plugin` | |
| 39 | + |
| 40 | +## Prerequisites (one-time, per machine) |
| 41 | + |
| 42 | +The reactor's `release` profile uses: |
| 43 | + |
| 44 | +1. **Central Portal credentials.** Get a User Token from |
| 45 | + <https://central.sonatype.com/account>. Add to `~/.m2/settings.xml`: |
| 46 | + |
| 47 | + ```xml |
| 48 | + <servers> |
| 49 | + <server> |
| 50 | + <id>central</id> |
| 51 | + <username>YOUR_USER_TOKEN_NAME</username> |
| 52 | + <password>YOUR_USER_TOKEN_VALUE</password> |
| 53 | + </server> |
| 54 | + </servers> |
| 55 | + ``` |
| 56 | + |
| 57 | + `<id>central</id>` must match `publishingServerId` in the reactor pom's |
| 58 | + `central-publishing-maven-plugin` config. |
| 59 | + |
| 60 | +2. **GPG signing key.** Maven Central requires signed JARs. The key used |
| 61 | + for 7.0.0 was generated batch-mode with `%no-protection` (no passphrase |
| 62 | + — operationally simplest; rotate via the revocation cert if it ever |
| 63 | + leaks). Generate with: |
| 64 | + |
| 65 | + ```bash |
| 66 | + cat > /tmp/gen.txt <<'EOF' |
| 67 | + %no-protection |
| 68 | + Key-Type: RSA |
| 69 | + Key-Length: 4096 |
| 70 | + Key-Usage: sign |
| 71 | + Subkey-Type: RSA |
| 72 | + Subkey-Length: 4096 |
| 73 | + Subkey-Usage: encrypt |
| 74 | + Name-Real: Douglas Mealing |
| 75 | + Name-Email: doug@dougmealing.com |
| 76 | + Expire-Date: 5y |
| 77 | + %commit |
| 78 | + EOF |
| 79 | + gpg --batch --generate-key /tmp/gen.txt && rm /tmp/gen.txt |
| 80 | + ``` |
| 81 | +
|
| 82 | + Upload the public half to all three keyservers Sonatype validates against: |
| 83 | +
|
| 84 | + ```bash |
| 85 | + FP=<your-key-fingerprint> |
| 86 | + gpg --keyserver hkps://keys.openpgp.org --send-keys $FP |
| 87 | + gpg --keyserver hkps://keyserver.ubuntu.com --send-keys $FP |
| 88 | + gpg --keyserver hkps://pgp.mit.edu --send-keys $FP |
| 89 | + ``` |
| 90 | +
|
| 91 | + Then back up `~/.gnupg/` + the revocation certificate in |
| 92 | + `~/.gnupg/openpgp-revocs.d/$FP.rev` to an offline location. **If you |
| 93 | + lose the secret key, the next release needs a new key + you lose |
| 94 | + key-continuity with prior releases.** |
| 95 | +
|
| 96 | +3. **GPG passphrase in settings.xml** — even when the key has no |
| 97 | + passphrase, the reactor pom interpolates `${gpg.passphrase}` so an |
| 98 | + empty property is required: |
| 99 | +
|
| 100 | + ```xml |
| 101 | + <profiles> |
| 102 | + <profile> |
| 103 | + <id>gpg-credentials</id> |
| 104 | + <activation><activeByDefault>true</activeByDefault></activation> |
| 105 | + <properties> |
| 106 | + <gpg.passphrase></gpg.passphrase> |
| 107 | + <gpg.keyname>YOUR_KEY_FINGERPRINT</gpg.keyname> |
| 108 | + </properties> |
| 109 | + </profile> |
| 110 | + </profiles> |
| 111 | + ``` |
| 112 | +
|
| 113 | + For keys WITH a passphrase, drop the value in between the tags. CI-style |
| 114 | + `-Dgpg.passphrase=...` on the command line also works but is visible |
| 115 | + to process listings. |
| 116 | +
|
| 117 | +4. **`groupId` ownership.** `com.metaobjects` is already verified for the |
| 118 | + user token from steps 1–3. Verify in the Central Portal UI under |
| 119 | + Namespaces if you ever need to re-check. |
| 120 | +
|
| 121 | +## Procedure for a new release |
| 122 | +
|
| 123 | +### 1. Land all in-scope work on `main` |
| 124 | +
|
| 125 | +Confirm `main` is at the tip you want to release from. Run the per-port |
| 126 | +test suites you care about. The Java metadata module has 4 known |
| 127 | +pre-existing conformance failures in `provider-extension-*` fixtures |
| 128 | +(they need provider IDs that aren't registered in the standard test |
| 129 | +classpath); these are unrelated to release content and tracked separately. |
| 130 | +The actual release deploy uses `-DskipTests` because of them — see step 5. |
| 131 | +
|
| 132 | +### 2. Bump versions across the tree |
| 133 | +
|
| 134 | +```bash |
| 135 | +cd server/java |
| 136 | +
|
| 137 | +# Reactor poms — versions:set only touches modules in <modules>. |
| 138 | +mvn versions:set -DnewVersion=7.X.Y -DgenerateBackupPoms=false |
| 139 | +
|
| 140 | +# Non-reactor poms (integration-tests*, docs, archetype, examples/*) declare |
| 141 | +# <parent> refs and need a sed pass: |
| 142 | +for f in integration-tests/pom.xml integration-tests-kotlin/pom.xml docs/pom.xml \ |
| 143 | + archetype/pom.xml examples/pom.xml examples/basic-example/pom.xml \ |
| 144 | + examples/osgi-example/pom.xml examples/shared-resources/pom.xml \ |
| 145 | + examples/spring-example/pom.xml; do |
| 146 | + [ -f "$f" ] && sed -i 's|<version>PREV-VERSION</version>|<version>7.X.Y</version>|' "$f" |
| 147 | +done |
| 148 | +
|
| 149 | +# Verify zero stragglers: |
| 150 | +grep -rln '<version>PREV-VERSION<' . --include='pom.xml' # should be empty |
| 151 | +``` |
| 152 | +
|
| 153 | +Commit the bump on `main`: |
| 154 | +
|
| 155 | +```bash |
| 156 | +git add server/java |
| 157 | +git commit -m "release(java): bump version to 7.X.Y" |
| 158 | +git push origin main |
| 159 | +``` |
| 160 | +
|
| 161 | +### 3. Local smoke build |
| 162 | +
|
| 163 | +```bash |
| 164 | +cd server/java && mvn install -DskipTests |
| 165 | +``` |
| 166 | +
|
| 167 | +All 14 publishable modules should reach SUCCESS. The build also installs |
| 168 | +locally into `~/.m2/repository/com/metaobjects/*/7.X.Y/`. |
| 169 | +
|
| 170 | +### 4. Tag the release |
| 171 | +
|
| 172 | +```bash |
| 173 | +git tag -a v7.X.Y -m "metaobjects Java 7.X.Y — <one-line summary>" |
| 174 | +git push origin v7.X.Y |
| 175 | +``` |
| 176 | +
|
| 177 | +### 5. Deploy to Central Portal |
| 178 | +
|
| 179 | +```bash |
| 180 | +cd server/java |
| 181 | +mvn deploy -P release -DperformRelease=true -DskipTests |
| 182 | +``` |
| 183 | +
|
| 184 | +`-DskipTests` is intentional — the 4 pre-existing provider-extension |
| 185 | +conformance failures (see step 1) would otherwise block the release. The |
| 186 | +test gate ran per-module during development for the actual content of |
| 187 | +this release. |
| 188 | +
|
| 189 | +What the command does: |
| 190 | +
|
| 191 | +- Activates the `release` profile (triggered by `-DperformRelease=true`). |
| 192 | +- Includes all 14 reactor modules (no `-pl` exclusions needed since |
| 193 | + archetype/examples are out of `<modules>`). |
| 194 | +- Generates javadoc + sources jars for each module. **Kotlin modules use |
| 195 | + `dokka-maven-plugin` to produce javadoc** (added to each Kotlin pom |
| 196 | + during 7.0.0 prep) — `maven-javadoc-plugin` alone scans Java only and |
| 197 | + Central rejects deployments with missing javadoc jars. |
| 198 | +- Signs every artifact via `maven-gpg-plugin`. |
| 199 | +- Stages to the Central Portal via `central-publishing-maven-plugin`. |
| 200 | +- Because `autoPublish: true` + `waitUntil: published`, the plugin waits |
| 201 | + until artifacts are LIVE on Central before returning. |
| 202 | +
|
| 203 | +Wall time: typically 1–3 min for the build + 3–10 min waiting on Central's |
| 204 | +validation + publish. **Once `BUILD SUCCESS` prints, the artifacts are |
| 205 | +LIVE and irreversible — no manual promotion step.** |
| 206 | +
|
| 207 | +### 6. Verify on Central |
| 208 | +
|
| 209 | +```bash |
| 210 | +curl -s https://repo.maven.apache.org/maven2/com/metaobjects/metaobjects-metadata/maven-metadata.xml \ |
| 211 | + | grep -E '<latest>|<release>' |
| 212 | +# Expected: |
| 213 | +# <latest>7.X.Y</latest> |
| 214 | +# <release>7.X.Y</release> |
| 215 | +
|
| 216 | +curl -sI https://repo.maven.apache.org/maven2/com/metaobjects/metaobjects-metadata/7.X.Y/metaobjects-metadata-7.X.Y.jar | head -1 |
| 217 | +# Expected: HTTP/2 200 |
| 218 | +``` |
| 219 | +
|
| 220 | +### 7. Post-release: bump to next SNAPSHOT |
| 221 | +
|
| 222 | +```bash |
| 223 | +cd server/java |
| 224 | +mvn versions:set -DnewVersion=7.X.Z-SNAPSHOT -DgenerateBackupPoms=false |
| 225 | +# Same sed pass for non-reactor poms (step 2): |
| 226 | +for f in integration-tests/pom.xml integration-tests-kotlin/pom.xml docs/pom.xml \ |
| 227 | + archetype/pom.xml examples/pom.xml examples/basic-example/pom.xml \ |
| 228 | + examples/osgi-example/pom.xml examples/shared-resources/pom.xml \ |
| 229 | + examples/spring-example/pom.xml; do |
| 230 | + [ -f "$f" ] && sed -i 's|<version>7.X.Y</version>|<version>7.X.Z-SNAPSHOT</version>|' "$f" |
| 231 | +done |
| 232 | +git add server/java |
| 233 | +git commit -m "chore(java): bump to 7.X.Z-SNAPSHOT post-7.X.Y" |
| 234 | +git push origin main |
| 235 | +``` |
| 236 | +
|
| 237 | +## Lessons from the 7.0.0 publish |
| 238 | +
|
| 239 | +These are the actual non-obvious gotchas that surfaced during the first |
| 240 | +deploy from the consolidated reactor: |
| 241 | +
|
| 242 | +- **`-pl '!examples'` does NOT transitively exclude `examples/*` children |
| 243 | + under Maven 3.6.3.** Either list every child explicitly |
| 244 | + (`-pl '!examples,!examples/basic-example,!examples/osgi-example,...'`) |
| 245 | + or remove the offending modules from the reactor entirely. 7.0.0 went |
| 246 | + with the latter — `archetype` and `examples` are no longer in the |
| 247 | + reactor's `<modules>` list. |
| 248 | +
|
| 249 | +- **Kotlin modules need Dokka for the javadoc jar.** Central's validation |
| 250 | + rejects any artifact without a javadoc.jar. `maven-javadoc-plugin` only |
| 251 | + scans Java sources, so Kotlin-only modules (`metadata-ktx`, |
| 252 | + `codegen-kotlin`, `omdb-ktx`) produce no jar without a Kotlin doc |
| 253 | + generator. `dokka-maven-plugin` (bound to the `package` phase via the |
| 254 | + `javadocJar` goal) fills the gap. Each of the three Kotlin modules has |
| 255 | + this plugin in their pom. |
| 256 | +
|
| 257 | +- **`-DskipTests` is needed today.** The Java metadata module has 4 |
| 258 | + `provider-extension-*` conformance test failures that pre-date the |
| 259 | + 7.0.0 release (failing on `main` independent of release content). Until |
| 260 | + those test fixtures are reconciled, releases skip tests at deploy time. |
| 261 | + Per-module test gates still run during development. |
| 262 | +
|
| 263 | +- **`autoPublish: true` means no staging review.** Once `mvn deploy` |
| 264 | + returns `BUILD SUCCESS`, the artifacts are LIVE on Central and cannot |
| 265 | + be deleted. To get a review window before going live, temporarily flip |
| 266 | + `autoPublish` to `false` in `server/java/pom.xml`, run the deploy, |
| 267 | + manually promote via the Central Portal UI, then revert. |
| 268 | +
|
| 269 | +- **Central reads from `repo.maven.apache.org` shortly after publish.** |
| 270 | + The plugin's `waitUntil: published` blocks until artifacts are visible |
| 271 | + there, so the post-deploy `curl` verification (step 6) returns 200 |
| 272 | + immediately. If you skip `waitUntil`, you'd need to wait a few minutes |
| 273 | + before verifying. |
| 274 | +
|
| 275 | +- **Maven Central rejects empty javadoc.jars structurally.** A common |
| 276 | + workaround for Kotlin/no-Java modules is to attach an empty |
| 277 | + `-javadoc.jar` via `maven-jar-plugin` with `<forceCreation>true</forceCreation>`. |
| 278 | + We didn't test whether Central accepts that today — we went with Dokka |
| 279 | + for proper docs instead. |
0 commit comments