|
| 1 | +# Spring Tools Release Process |
| 2 | + |
| 3 | +Spring Tools ships three independent release tracks that all build on the |
| 4 | +Java language servers but are released, tested, and published on their own |
| 5 | +schedules: |
| 6 | + |
| 7 | +- **Eclipse track** — Eclipse LS Extensions → Eclipse Distribution → Test → Publish |
| 8 | +- **VSCode track** — VSCode Extensions RC → Test → Publish |
| 9 | +- **Claude Code plugin track** — Test locally → Release (build + publish) |
| 10 | + |
| 11 | +The Eclipse and VSCode tracks each have their own release-candidate loop: if |
| 12 | +testing turns up a problem in one track, only that track's release step is |
| 13 | +re-spun. The other track keeps whatever candidate it already has and does |
| 14 | +not need to be rebuilt. The Claude Code plugin track has no candidate/staging |
| 15 | +step at all — see below. |
| 16 | + |
| 17 | +Every step below except "Test" is a GitHub Actions workflow, triggered via |
| 18 | +`workflow_dispatch` from the Actions tab (or `gh workflow run`). |
| 19 | + |
| 20 | +## Prerequisite: Update Changelogs |
| 21 | + |
| 22 | +Before kicking off *any* of the three tracks below, the changelog entries |
| 23 | +for this release must be written and pushed: |
| 24 | + |
| 25 | +- the wiki's `Changelog.md` (in the `spring-tools.wiki` repo) |
| 26 | +- the `CHANGELOG.md` of whichever VSCode extension(s) are being released |
| 27 | + (typically just [`vscode-extensions/vscode-spring-boot/CHANGELOG.md`](vscode-extensions/vscode-spring-boot/CHANGELOG.md)) |
| 28 | + |
| 29 | +A draft is generated by the [`changelog-draft.yml`](.github/workflows/changelog-draft.yml) |
| 30 | +workflow (input: `release`, e.g. `4.20.1.RELEASE`). Despite its "Push to |
| 31 | +wiki" step name, it doesn't actually commit or push anything — it checks |
| 32 | +out the wiki, runs `github-changelog-generator` against the given release |
| 33 | +marker, prepends the result to a local copy of `Changelog.md`, and just |
| 34 | +prints it to the job log. That printed content has to be copied by hand, |
| 35 | +reformatted to match the existing entries' style (grouped bullet points |
| 36 | +tagged by component, e.g. `_(Spring Boot)_`), and manually committed/pushed |
| 37 | +to both the wiki and the relevant extension `CHANGELOG.md` file(s). This |
| 38 | +manual reformatting step is a good candidate for AI-assisted automation. |
| 39 | + |
| 40 | +## Eclipse Track |
| 41 | + |
| 42 | +| # | Step | Workflow | Key inputs | |
| 43 | +|---|------|----------|------------| |
| 44 | +| 1 | Release Eclipse LS Extensions | [`release-eclipse-ls-extensions.yml`](.github/workflows/release-eclipse-ls-extensions.yml) | `sts-language-servers-version` — the date-based suffix of the p2 repo S3 path, e.g. `2026-09-03` | |
| 45 | +| 2 | Release Eclipse Distribution | [`release-eclipse-distro-build.yml`](.github/workflows/release-eclipse-distro-build.yml) | `sts-language-servers-version` — **same date as step 1**; `qualifier` — `RELEASE` (default) | |
| 46 | +| 3 | Test | — manual | Install/verify the staged Eclipse distro build | |
| 47 | +| 4 | Publish Eclipse Distribution | [`publish-eclipse-release.yml`](.github/workflows/publish-eclipse-release.yml) | `ls_version` (same date as step 1), `release_version` (e.g. `4.20.1`), `qualifier`, `eclipse_latest` (e.g. `e4.40`) | |
| 48 | + |
| 49 | +Step 2 depends on the language-server date produced in step 1. Unlike the |
| 50 | +VSCode track, there is no RC-numbered build for Eclipse: step 2 builds and |
| 51 | +stages a single `RELEASE` p2 repo. If testing finds a distro-only problem, |
| 52 | +re-running step 2 with the same inputs simply overwrites that staged build |
| 53 | +in place — there's nothing to increment. Step 1 only needs to be re-run if |
| 54 | +the language servers themselves are the problem. Step 4 doesn't build |
| 55 | +anything new; it just aggregates/publishes whatever is currently staged |
| 56 | +from step 2. |
| 57 | + |
| 58 | +```mermaid |
| 59 | +stateDiagram-v2 |
| 60 | + [*] --> ReleaseLSExtensions |
| 61 | + ReleaseLSExtensions --> ReleaseDistro : ls-version (date) |
| 62 | + ReleaseDistro --> TestEclipse |
| 63 | + TestEclipse --> ReleaseDistro : issues found\n(re-spin: overwrites staged build) |
| 64 | + TestEclipse --> PublishDistro : tests pass |
| 65 | + PublishDistro --> [*] |
| 66 | +``` |
| 67 | + |
| 68 | +## VSCode Track |
| 69 | + |
| 70 | +| # | Step | Workflow | Key inputs | |
| 71 | +|---|------|----------|------------| |
| 72 | +| 1 | Release VSCode Extensions RC | [`release-vscode-extension.yml`](.github/workflows/release-vscode-extension.yml) | `extension-name` (`vscode-spring-boot`), `postfix` (e.g. `RC1`), `tag` | |
| 73 | +| 2 | Test | — manual | Install/verify the built `.vsix` RC build | |
| 74 | +| 3 | Publish VSCode Extension | [`publish-vscode-extension.yml`](.github/workflows/publish-vscode-extension.yml) | `extension-name`, `version`, `postfix` — **the exact RC you're promoting to release** (e.g. `RC1`); it downloads that specific `<extension-name>-<version>-<postfix>.vsix` and publishes it as-is to both the VSCode Marketplace and Open VSX | |
| 75 | + |
| 76 | +This is per-extension, not a bulk step: run it once for whichever extension |
| 77 | +you're publishing. `vscode-spring-boot` is the extension that goes through |
| 78 | +this release process regularly; the other VSCode extensions |
| 79 | +(`vscode-concourse`, `vscode-manifest-yaml`, `vscode-bosh`, `boot-dev-pack`) |
| 80 | +use the same two workflows but are released far less frequently. Note that |
| 81 | +publishing doesn't relabel the artifact as `RELEASE` — the same RC-suffixed |
| 82 | +`.vsix` you tested is what goes out to the marketplaces, so the `postfix` |
| 83 | +you pass here must match whichever RC actually passed testing. |
| 84 | + |
| 85 | +If testing finds an issue, re-spin step 1 with an incremented `postfix` |
| 86 | +(`RC1` → `RC2`, …), then test again. |
| 87 | + |
| 88 | +```mermaid |
| 89 | +stateDiagram-v2 |
| 90 | + [*] --> ReleaseVSCodeRC |
| 91 | + ReleaseVSCodeRC --> TestVSCode |
| 92 | + TestVSCode --> ReleaseVSCodeRC : issues found\n(re-spin: RC1 -> RC2 -> ...) |
| 93 | + TestVSCode --> PublishVSCode : tests pass |
| 94 | + PublishVSCode --> [*] |
| 95 | +``` |
| 96 | + |
| 97 | +## Claude Code Plugin Track |
| 98 | + |
| 99 | +The `spring-tools` Claude Code plugin (`claude-plugins/spring-tools`) bundles |
| 100 | +the standalone Spring Boot language server (Jandex-based, no JDT LS) as an |
| 101 | +MCP server for Claude Code. Its release process is different from the other |
| 102 | +two tracks: there is no RC/staging step — the release workflow builds *and* |
| 103 | +publishes atomically in one shot, so verification has to happen locally |
| 104 | +*before* that workflow is dispatched. |
| 105 | + |
| 106 | +| # | Step | Workflow | Key inputs | |
| 107 | +|---|------|----------|------------| |
| 108 | +| 1 | Test locally | — manual | Run [`claude-plugins/update-local-jars.sh`](claude-plugins/update-local-jars.sh) to build the standalone LS jar from the current `headless-services` snapshot and drop it into `spring-tools/language-server/`, then exercise the plugin through the local `spring-tools-local` marketplace ([`claude-plugins/.claude-plugin/marketplace.json`](claude-plugins/.claude-plugin/marketplace.json)) | |
| 109 | +| 2 | Release (build + publish) | [`release-standalone-ls.yml`](.github/workflows/release-standalone-ls.yml) | `dist: release` — builds the standalone LS jar, uploads it to the CDN, tags the repo `v<version>`, and updates the release marketplace's `marketplace.json` to point at that tag | |
| 110 | + |
| 111 | +(Nightly/on-demand snapshot builds of the plugin are handled separately by |
| 112 | +[`snapshot-standalone-ls.yml`](.github/workflows/snapshot-standalone-ls.yml) |
| 113 | +and are not part of this manual release process.) |
| 114 | + |
| 115 | +```mermaid |
| 116 | +stateDiagram-v2 |
| 117 | + [*] --> TestLocally |
| 118 | + TestLocally --> TestLocally : issues found\n(fix and re-test locally) |
| 119 | + TestLocally --> ReleaseAndPublish : tests pass |
| 120 | + ReleaseAndPublish --> [*] |
| 121 | +``` |
| 122 | + |
| 123 | +## Combined view |
| 124 | + |
| 125 | +The three tracks run independently and converge only in the sense that all |
| 126 | +are cut, tested, and published for the same overall Spring Tools release — |
| 127 | +not that any one of them blocks the others. |
| 128 | + |
| 129 | +```mermaid |
| 130 | +stateDiagram-v2 |
| 131 | + state "Eclipse Track" as Eclipse { |
| 132 | + [*] --> ReleaseLSExtensions |
| 133 | + ReleaseLSExtensions --> ReleaseDistro |
| 134 | + ReleaseDistro --> TestEclipse |
| 135 | + TestEclipse --> ReleaseDistro : issues found\n(overwrites staged build) |
| 136 | + TestEclipse --> PublishDistro : pass |
| 137 | + PublishDistro --> [*] |
| 138 | + } |
| 139 | + state "VSCode Track" as VSCode { |
| 140 | + [*] --> ReleaseVSCodeRC |
| 141 | + ReleaseVSCodeRC --> TestVSCode |
| 142 | + TestVSCode --> ReleaseVSCodeRC : issues found\n(RC1 -> RC2 -> ...) |
| 143 | + TestVSCode --> PublishVSCode : pass |
| 144 | + PublishVSCode --> [*] |
| 145 | + } |
| 146 | + state "Claude Code Plugin Track" as ClaudePlugin { |
| 147 | + [*] --> TestLocally |
| 148 | + TestLocally --> TestLocally : issues found |
| 149 | + TestLocally --> ReleaseAndPublish : pass |
| 150 | + ReleaseAndPublish --> [*] |
| 151 | + } |
| 152 | +``` |
| 153 | + |
| 154 | +## Announcements |
| 155 | + |
| 156 | +Once all three tracks above have been published, the release is announced |
| 157 | +in four places. This is the last step and covers the whole release, not any |
| 158 | +one track: |
| 159 | + |
| 160 | +| Channel | How | |
| 161 | +|---|---| |
| 162 | +| Eclipse Marketplace | manual — update the listing at marketplace.eclipse.org to point at the new Eclipse distribution | |
| 163 | +| spring.io release page | manual — PR against [`spring-io/spring-website-content`](https://github.com/spring-io/spring-website-content) | |
| 164 | +| Social media | manual | |
| 165 | +| GChat `Spring-Releases` space | [`announce-release.yml`](.github/workflows/announce-release.yml) — input `version` (e.g. `4.20.1.RELEASE`); also posts a notice to the internal Spring Tools team GChat space | |
| 166 | + |
| 167 | +## Prepare the Repo for the Next Version |
| 168 | + |
| 169 | +The very last step, after announcing, is bumping `main` to the next |
| 170 | +`-SNAPSHOT` version so every track above is already sitting on an |
| 171 | +un-released version by the time the next release cycle starts. This is a |
| 172 | +manual, multi-file edit (no script or workflow does it) committed as one |
| 173 | +commit — see e.g. `afd629304` ("Bump 2.3.0 5.3.0") or `df8038dc0` ("Bump to |
| 174 | +5.4.0") for the pattern to replicate. Each version below is bumped to the |
| 175 | +next **minor**: |
| 176 | + |
| 177 | +- **Language servers (`headless-services`)** — the `commons-parent` version |
| 178 | + (e.g. `2.4.0-SNAPSHOT` → `2.5.0-SNAPSHOT`) is hardcoded in every |
| 179 | + `headless-services/**/pom.xml`'s own `<version>` and `<parent><version>` |
| 180 | + and has to be edited in each one by hand. |
| 181 | +- **Eclipse plugins** — the parallel `5.x` scheme (e.g. |
| 182 | + `5.4.0-SNAPSHOT` → `5.5.0-SNAPSHOT`), hardcoded across |
| 183 | + `eclipse-language-servers`, `eclipse-extensions`, and |
| 184 | + `eclipse-distribution`: every `pom.xml` `<version>`, every `MANIFEST.MF` |
| 185 | + `Bundle-Version`, every `feature.xml` `version`, and the `.product` file. |
| 186 | +- **VSCode extensions** — bump `version` in `package.json` (and the |
| 187 | + corresponding `CHANGELOG.md` heading) **only for the extension(s) that |
| 188 | + were actually released this cycle** — normally just |
| 189 | + [`vscode-extensions/vscode-spring-boot/package.json`](vscode-extensions/vscode-spring-boot/package.json). |
| 190 | + Extensions that weren't released (`vscode-concourse`, |
| 191 | + `vscode-manifest-yaml`, `vscode-bosh`, `boot-dev-pack`) are left alone. |
| 192 | +- **Claude Code plugin** — bump `version` in |
| 193 | + [`claude-plugins/spring-tools/.claude-plugin/plugin.json`](claude-plugins/spring-tools/.claude-plugin/plugin.json) |
| 194 | + to match the new `headless-services` version. This is exactly what makes |
| 195 | + the [Claude Code Plugin Track](#claude-code-plugin-track) work: it leaves |
| 196 | + `main` on a version that has never been tagged, ready for the next |
| 197 | + `release-standalone-ls.yml` run. |
| 198 | + |
| 199 | +**Conditionally — only if a new Eclipse release train shipped this |
| 200 | +cycle** — adopt the new target platform: |
| 201 | + |
| 202 | +- Add a new `<profile>` (e.g. `e442`) to |
| 203 | + [`eclipse-language-servers/pom.xml`](eclipse-language-servers/pom.xml) |
| 204 | + and [`eclipse-distribution/pom.xml`](eclipse-distribution/pom.xml), |
| 205 | + mirroring the existing `e440`/`e441` blocks (`dist.target`, |
| 206 | + `dist.target.major`, `dist.platform.name`, `dist.platform.name.long`). |
| 207 | +- Update the workflows that hardcode which `eclipse_profile`(s) to build: |
| 208 | + [`release-eclipse-ls-extensions.yml`](.github/workflows/release-eclipse-ls-extensions.yml) |
| 209 | + (currently pinned to `e440` only), |
| 210 | + [`release-eclipse-distro-build.yml`](.github/workflows/release-eclipse-distro-build.yml) |
| 211 | + (currently builds both `e441` and `e440`, with an older `e435` job already |
| 212 | + commented out — that's the pattern for retiring a train), and |
| 213 | + `snapshot-all.yml`. `gh-hosted-eclipse-distro-build.yml` and |
| 214 | + `eclipse-ls-extensions-build.yml` themselves don't need changes — they're |
| 215 | + reusable workflows that just take `eclipse_profile` as an input. |
| 216 | +- Also worth checking: `CLAUDE.md`'s "Eclipse profile options" line is |
| 217 | + currently stale (`e439`/`e440`) relative to what the poms and release |
| 218 | + workflows actually build (`e440`/`e441`) — keep it in sync when adopting |
| 219 | + a new train. |
| 220 | + |
| 221 | +Given how tedious and error-prone the manual multi-file version bumps above |
| 222 | +are, this whole step is another good candidate for automation. |
0 commit comments