From 4ac30cb9883164f7940d5188a144a1d6ea82a964 Mon Sep 17 00:00:00 2001 From: Hendrik Ebbers Date: Thu, 12 Nov 2026 23:43:28 +0100 Subject: [PATCH 1/2] docs(spec): add spec 004 for Spring Boot 4 Co-Authored-By: Claude Opus 5.5 --- docs/specs/004-spring-boot-4/behaviors.md | 70 +++++++++++++ docs/specs/004-spring-boot-4/design.md | 120 ++++++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100644 docs/specs/004-spring-boot-4/behaviors.md create mode 100644 docs/specs/004-spring-boot-4/design.md diff --git a/docs/specs/004-spring-boot-4/behaviors.md b/docs/specs/004-spring-boot-4/behaviors.md new file mode 100644 index 0000000..943d3b9 --- /dev/null +++ b/docs/specs/004-spring-boot-4/behaviors.md @@ -0,0 +1,70 @@ +# Behaviors: Spring Boot 4 + +Scenarios describe a project inheriting `java-parent` 2.0.0 ("consumer"). + +## Version management + +### Spring Boot 4 is managed + +- **Given** a consumer that declares `spring-boot-starter-webmvc` without a `` +- **When** the project resolves its dependencies +- **Then** Spring Boot 4.1.2 and Spring Framework 7.0.10 are used + +### The OpenAPI stack matches Boot 4 + +- **Given** a consumer that declares `springdoc-openapi-starter-webmvc-ui` without a + `` +- **When** `mvn dependency:tree -Dincludes=org.springdoc,io.swagger.core.v3,org.webjars:swagger-ui` + is run +- **Then** springdoc resolves to 3.2.0 +- **And** `swagger-annotations-jakarta`, `swagger-models-jakarta` and + `swagger-core-jakarta` all resolve to 2.2.55 +- **And** `swagger-ui` resolves to 5.34.0 + +### Testcontainers is unchanged + +- **Given** a consumer that declares `testcontainers-junit-jupiter` without a `` +- **When** the project resolves its dependencies +- **Then** Testcontainers 2.0.5 is used + +### jspecify is not pinned below Boot + +- **Given** a consumer that declares `org.jspecify:jspecify` without a `` +- **When** the project resolves its dependencies +- **Then** jspecify 1.0.1 is used — the version Boot 4.1.2 manages — with `compile` + scope + +## Java baseline + +### Sources compile for Java 25 + +- **Given** a consumer with one Java source file and no compiler configuration +- **When** it is built on JDK 25 +- **Then** the class file has major version 69 (Java 25) + +### An older JDK fails fast + +- **Given** the same consumer +- **When** it is built on JDK 21 +- **Then** the build fails in `validate` with the `requireJavaVersion` enforcer rule +- **And** no compilation is attempted + +### A child may still raise the baseline + +- **Given** a consumer that sets `enforcer.requiredJavaVersion` to a higher version +- **When** it is built on a JDK below that version +- **Then** the enforcer fails, as in 1.x + +## The parent itself + +### The parent builds and validates on JDK 25 + +- **Given** this repository on JDK 25 +- **When** `./mvnw -Pfull-build clean verify` is run +- **Then** the build passes, including the enforcer, pomchecker and the CycloneDX SBOM + +### Reproducible-build settings are unchanged + +- **Given** the 2.0.0-SNAPSHOT POM +- **When** `project.build.outputTimestamp` is evaluated +- **Then** it is the literal inherited from the 1.3.0 release, untouched by this change diff --git a/docs/specs/004-spring-boot-4/design.md b/docs/specs/004-spring-boot-4/design.md new file mode 100644 index 0000000..e39557b --- /dev/null +++ b/docs/specs/004-spring-boot-4/design.md @@ -0,0 +1,120 @@ +# Design: Spring Boot 4 + +## GitHub Issue + +[#8 — Upgrade managed Spring Boot to 4.1](https://github.com/OpenElementsLabs/java-parent/issues/8) + +## Summary + +`java-parent` 1.x manages Spring Boot 3.5.14. Downstream projects (first of all +spring-services, OpenElementsLabs/spring-services#58) are moving to Spring Boot 4 and +deliberately do not override the Boot version themselves: a `spring-boot.version` +property in a child would shadow the parent's and silently pin its BOM import. A parent +release that manages Boot 4 is therefore a prerequisite for that move. + +Every consumer of such a parent must be on Boot 4, so this is a new major version: +**`java-parent` 2.0.0**. The version lines are: + +| `java-parent` | Spring Boot | Java baseline | +|---|---|---| +| 1.x | 3.5 | 21 | +| 2.x | 4.1 | 25 | + +Projects that stay on Boot 3.5 keep using 1.x. No further 1.x release is planned by +this spec. + +## Goals + +- The parent manages Spring Boot 4.1.2 and an OpenAPI stack built against it. +- A child moves from 1.x to 2.0.0 by changing only `` and its JDK — + the parent makes the Java baseline decision, children make none of their own. +- The managed versions stay aligned with what Boot 4.1.2 itself manages, so the parent + never pins a coordinate *below* Boot's choice. + +## Non-goals + +- **No migration of consumer code.** Boot 4's own breaking changes (renamed starters, + Jackson 3, Spring Framework 7, Hibernate 7) are the consumer's concern and are + handled in the consumer's own upgrade (e.g. spring-services spec 025). +- **No management of `spring-boot-maven-plugin`.** A `scope=import` BOM contributes + `dependencyManagement` only, never `pluginManagement`, so the plugin was never + inherited through `spring-boot-dependencies` — in 1.x neither. Children declare it + with an explicit version, as before. Unchanged by this spec. +- **No maintenance branch for 1.x.** Created only if a 1.x fix is actually needed. + +## Version changes + +| Property | 1.x | 2.0.0 | Source of truth | +|---|---|---|---| +| `spring-boot.version` | 3.5.14 | **4.1.2** | Brings Spring Framework 7.0.10, Hibernate 7.4.13.Final, Jackson 3.1.7 | +| `springdoc.version` | 2.8.17 | **3.2.0** | springdoc 2.x supports Boot 3 only; 3.2.0's aggregator POM has `spring-boot-starter-parent` 4.1.2 as parent | +| `swagger.version` | 2.2.47 | **2.2.55** | `` in `springdoc-openapi-3.2.0.pom` | +| `swagger-ui.version` | 5.32.2 | **5.34.0** | `` in `springdoc-openapi-3.2.0.pom` | +| `testcontainers.version` | 2.0.5 | 2.0.5 | Identical to the version Boot 4.1.2 manages | +| `jspecify.version` | 1.0.0 | **1.0.1** | Boot 4.1.2 manages 1.0.1 | +| `maven.compiler.source` / `target` | 21 | **25** | Java baseline decision, see below | +| `enforcer.requiredJavaVersion` | 21 | **25** | Follows the baseline | + +### jspecify: not in the issue, but required by the third goal + +The issue lists jspecify as unchanged at 1.0.0. Boot 4.1.2's BOM manages +`org.jspecify:jspecify` at **1.0.1**, and the parent's explicit `dependencyManagement` +entry takes precedence over any imported BOM. Keeping 1.0.0 would therefore actively +*downgrade* jspecify below what Spring Framework 7 and Boot 4 were built and tested +with. 1.0.1 is a patch release; the explicit entry is kept because it also sets +`scope=compile`. + +### Java baseline: 25 + +Boot 4 requires Java 17+; both 21 and 25 are LTS. 25 was chosen so that the major +version that already forces every consumer through a framework migration also carries +the JDK move, instead of a second breaking parent release later. The cost: consumers +must upgrade their JDK together with Boot 4. The decision lives in the parent — children +inherit `maven.compiler.*` and `enforcer.requiredJavaVersion` and do not set them. + +Consequences inside this repository: the CI workflows (`build.yml`, `snapshot.yml`, +`release.yml`) and `.sdkmanrc` move to Java 25, because the enforcer rule would +otherwise fail this project's own build. + +## Consumer notes (for the release notes) + +- **2.x = Spring Boot 4, 1.x = Spring Boot 3.5.** +- **Java 25 is required** (enforced in `validate`). +- **Jackson 2 next to Jackson 3.** `swagger-core` 2.2.55, used by springdoc 3.2.0, + still depends on `com.fasterxml.jackson.core:jackson-databind` (Jackson 2), while + Boot 4 uses Jackson 3 (`tools.jackson`). Applications using springdoc have both on + the runtime classpath. Libraries using only `swagger-annotations-jakarta` are not + affected — it has no dependencies. + +## Files changed + +| File | Change | +|---|---| +| `pom.xml` | Version `2.0.0-SNAPSHOT`; properties per the table above | +| `README.md` | Version lines table, updated managed versions, Java 25, Boot 4 pairing in the OpenAPI reference list | +| `.github/workflows/*.yml` | `java-version: "25"` | +| `.sdkmanrc` | `java=25` | + +## Regression risk + +- **Every consumer breaks on upgrade by design.** Boot 4 and Java 25 are both breaking. + The major version number is the signal; 1.x stays available. +- **The OpenAPI lockstep from spec 003 applies unchanged.** The three values were read + from springdoc 3.2.0's aggregator POM; a future springdoc bump needs the same lookup. +- **No overlap with the Boot BOM.** Boot 4.1.2 manages neither springdoc nor Swagger, so + the OpenAPI imports do not compete with it. + +## Acceptance + +`java-parent` has no Java sources, so acceptance is a manual check against a throwaway +probe child (same approach as specs 001 and 003): + +1. `./mvnw -Pfull-build clean verify` passes on JDK 25 (enforcer, pomchecker, SBOM). +2. `./mvnw -N install`, then a probe child inheriting `2.0.0-SNAPSHOT` with + `spring-boot-starter-webmvc`, `springdoc-openapi-starter-webmvc-ui`, `jspecify` and + `testcontainers-junit-jupiter` (all without ``) plus one Java source file. +3. `dependency:tree` shows Boot 4.1.2, Spring Framework 7.0.10, springdoc 3.2.0, all + Swagger jakarta artifacts at 2.2.55, `swagger-ui` 5.34.0, jspecify 1.0.1, + Testcontainers 2.0.5. +4. The probe compiles with release 25 (class file major version 69). +5. Running the probe on JDK 21 fails in `validate` with the enforcer message. From be7f04af7d934281c43671d6d1e1292a876c64e5 Mon Sep 17 00:00:00 2001 From: Hendrik Ebbers Date: Thu, 12 Nov 2026 23:43:28 +0100 Subject: [PATCH 2/2] feat(pom)!: manage Spring Boot 4.1 and require Java 25 java-parent 2.0.0 manages Spring Boot 4.1.2 together with an OpenAPI stack built against it (springdoc 3.2.0, Swagger 2.2.55, Swagger UI 5.34.0) and raises jspecify to 1.0.1, the version Boot 4.1.2 manages, so the parent's explicit entry no longer pins it below Boot. The Java baseline moves from 21 to 25 for compiler and enforcer; CI and .sdkmanrc follow. 1.x remains the Spring Boot 3.5 / Java 21 line. BREAKING CHANGE: consumers must be on Spring Boot 4 and Java 25. Closes #8 Co-Authored-By: Claude Opus 5.5 --- .github/workflows/build.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/snapshot.yml | 2 +- .sdkmanrc | 2 +- README.md | 34 ++++++++--- docs/specs/004-spring-boot-4/steps.md | 84 +++++++++++++++++++++++++++ docs/specs/INDEX.md | 1 + pom.xml | 22 ++++--- 8 files changed, 129 insertions(+), 20 deletions(-) create mode 100644 docs/specs/004-spring-boot-4/steps.md diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6d10325..4898594 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ jobs: - uses: actions/checkout@v6 - uses: actions/setup-java@v5 with: - java-version: "21" + java-version: "25" distribution: "temurin" - name: Cache Maven dependencies uses: actions/cache@v5 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 647964e..426a3b8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,7 +33,7 @@ jobs: - name: Setup Java uses: actions/setup-java@v5 with: - java-version: "21" + java-version: "25" distribution: "temurin" - name: Cache Maven dependencies diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml index dd93e6b..aa0c017 100644 --- a/.github/workflows/snapshot.yml +++ b/.github/workflows/snapshot.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v6 - uses: actions/setup-java@v5 with: - java-version: "21" + java-version: "25" distribution: "temurin" # Generate a settings.xml with a # whose credentials come from the env vars below. The id must match diff --git a/.sdkmanrc b/.sdkmanrc index 4b8f68b..b346dae 100644 --- a/.sdkmanrc +++ b/.sdkmanrc @@ -1 +1 @@ -java=21 +java=25 diff --git a/README.md b/README.md index ad22849..5de704a 100644 --- a/README.md +++ b/README.md @@ -13,18 +13,37 @@ boilerplate into every project. com.open-elements java-parent - 1.0.0 + 2.0.0 ``` Released artifacts are published to [Maven Central](https://central.sonatype.com/artifact/com.open-elements/java-parent); `-SNAPSHOT` builds are published to the Central Portal snapshot repository. +### Version lines + +The Spring Boot major version defines the `java-parent` major version: + +| `java-parent` | Spring Boot | Java | +|---------------|-------------|------| +| 2.x | 4.1 | 25 | +| 1.x | 3.5 | 21 | + +Projects that stay on Spring Boot 3.5 keep using 1.x. Upgrading to 2.x means moving +to Spring Boot 4 and Java 25 at the same time; the parent makes the Java baseline +decision, child projects do not set `maven.compiler.*` themselves. + +Applications that use springdoc get **Jackson 2 next to Jackson 3** at runtime: +`swagger-core` (used by springdoc 3.2.0) still depends on +`com.fasterxml.jackson.core:jackson-databind`, while Spring Boot 4 uses Jackson 3 +(`tools.jackson`). Projects that use only `swagger-annotations-jakarta` are not +affected — it has no dependencies. + ## Requirements | Tool | Version | Enforced by | |-------|------------|--------------------------------------------------------------| -| Java | 21 | `maven-enforcer-plugin` (`requireJavaVersion`) | +| Java | 25 | `maven-enforcer-plugin` (`requireJavaVersion`) | | Maven | 3.9.11+ | `maven-enforcer-plugin` (`requireMavenVersion`) + wrapper | The build fails fast in the `validate` phase if the local toolchain does not @@ -42,10 +61,10 @@ installation is required. Import-scoped BOMs so child projects can declare these dependencies **without a ``**: -- **Spring Boot** — `spring-boot-dependencies` (`3.5.14`) +- **Spring Boot** — `spring-boot-dependencies` (`4.1.2`) - **Testcontainers** — `testcontainers-bom` (`2.0.5`) -- **OpenAPI** — `springdoc-openapi-bom` (`2.8.17`) and `swagger-bom` (`2.2.47`), - plus `org.webjars:swagger-ui` (`5.32.2`) — see +- **OpenAPI** — `springdoc-openapi-bom` (`3.2.0`) and `swagger-bom` (`2.2.55`), + plus `org.webjars:swagger-ui` (`5.34.0`) — see [The OpenAPI stack](#the-openapi-stack) ### The OpenAPI stack @@ -99,7 +118,8 @@ org/springdoc/springdoc-openapi//springdoc-openapi-.pom ``` For reference: springdoc `2.8.6` pairs with Swagger `2.2.29` and Swagger UI `5.20.1`; -springdoc `2.8.17` pairs with `2.2.47` and `5.32.2`. +springdoc `2.8.17` pairs with `2.2.47` and `5.32.2` (Spring Boot 3, `java-parent` 1.x); +springdoc `3.2.0` pairs with `2.2.55` and `5.34.0` (Spring Boot 4, `java-parent` 2.x). ### Pinned plugin versions @@ -112,7 +132,7 @@ builds are reproducible and free of "you should pin this plugin" warnings: ### Build conventions -- **Java 21**, source/target via `maven.compiler.*`. +- **Java 25**, source/target via `maven.compiler.*`. - **UTF-8** for sources and reporting. - **`-parameters`** compiler flag (parameter names retained — useful for frameworks like Spring and Jackson). diff --git a/docs/specs/004-spring-boot-4/steps.md b/docs/specs/004-spring-boot-4/steps.md new file mode 100644 index 0000000..14becb4 --- /dev/null +++ b/docs/specs/004-spring-boot-4/steps.md @@ -0,0 +1,84 @@ +# Implementation Steps: Spring Boot 4 + +Spec: [`design.md`](design.md) · [`behaviors.md`](behaviors.md) · Issue #8 + +`java-parent` has `packaging=pom` and no Java sources, so the scenarios are exercised +against a throwaway probe child, per [Acceptance](design.md#acceptance). + +--- + +## Step 1: Bump the managed versions and the Java baseline + +- [x] Project version `2.0.0-SNAPSHOT` +- [x] `spring-boot.version` 4.1.2, `springdoc.version` 3.2.0, `swagger.version` 2.2.55, + `swagger-ui.version` 5.34.0, `jspecify.version` 1.0.1 +- [x] `maven.compiler.source` / `target` and `enforcer.requiredJavaVersion` 25 +- [x] Comments: Boot major defines the parent major; jspecify must not fall below Boot + +**Acceptance criteria:** +- [x] All six BOM/artifact versions exist on Maven Central +- [x] Swagger values match `` / `` in + `springdoc-openapi-3.2.0.pom`; its parent is `spring-boot-starter-parent` 4.1.2 +- [x] `project.build.outputTimestamp` unchanged (`2026-09-10T17:19:06Z`) + +**Related behaviors:** Spring Boot 4 is managed · The OpenAPI stack matches Boot 4 · +jspecify is not pinned below Boot · Reproducible-build settings are unchanged + +--- + +## Step 2: Move this repository's own toolchain to Java 25 + +- [x] `java-version: "25"` in `build.yml`, `snapshot.yml`, `release.yml` +- [x] `.sdkmanrc` → `java=25` + +**Acceptance criteria:** +- [x] `./mvnw -Pfull-build clean verify` passes on Temurin 25.0.1 + +**Related behaviors:** The parent builds and validates on JDK 25 + +--- + +## Step 3: Prove the result against a probe child + +- [x] `./mvnw -N install`, probe child with `spring-boot-starter-webmvc`, + `springdoc-openapi-starter-webmvc-ui`, `jspecify`, `testcontainers-junit-jupiter` + (no versions) and one record using `@Nullable` +- [x] Resolve the tree, compile on JDK 25, validate on JDK 21 + +**Acceptance criteria:** +- [x] Tree matches the expected versions (see coverage table) +- [x] `Hello.class` major version 69 +- [x] JDK 21: `RequireJavaVersion` fails — "version 21.0.11 which is not in the allowed + range [25,)" + +--- + +## Step 4: Documentation + +- [x] README: version lines table (2.x = Boot 4.1 / Java 25, 1.x = Boot 3.5 / Java 21), + Jackson 2 + 3 note, managed versions, Java 25, springdoc 3.2.0 pairing +- [x] `docs/specs/INDEX.md` entry + +The upgrade guide `docs/releases/upgrade-to-2.0.0.md` is generated by `release.sh` +(`/release-doc`) when 2.0.0 is cut; the design's +[Consumer notes](design.md#consumer-notes-for-the-release-notes) are its input. + +--- + +## Behavior Coverage + +| Scenario | Verification | Step | +|---|---|---| +| Spring Boot 4 is managed | Measured — `spring-boot` 4.1.2, `spring-core` 7.0.10 | 3 | +| The OpenAPI stack matches Boot 4 | Measured — springdoc 3.2.0, Swagger jakarta ×3 at 2.2.55, `swagger-ui` 5.34.0 | 3 | +| Testcontainers is unchanged | Measured — 2.0.5 | 3 | +| jspecify is not pinned below Boot | Measured — 1.0.1, `compile` | 3 | +| Sources compile for Java 25 | Measured — class file major 69 | 3 | +| An older JDK fails fast | Measured — enforcer failure on 21.0.11 | 3 | +| A child may still raise the baseline | Unchanged mechanism from 1.x; not re-measured | — | +| The parent builds and validates on JDK 25 | `./mvnw -Pfull-build clean verify` | 2 | +| Reproducible-build settings are unchanged | `help:evaluate` → `2026-09-10T17:19:06Z` | 1 | + +Also observed: springdoc 3.2.0 brings `com.fasterxml.jackson.core:jackson-databind` +2.21.7 next to Boot's `tools.jackson.core:jackson-databind` 3.1.7 — the Jackson 2 + 3 +situation the consumer notes describe. diff --git a/docs/specs/INDEX.md b/docs/specs/INDEX.md index 4f4c4bd..aa070c7 100644 --- a/docs/specs/INDEX.md +++ b/docs/specs/INDEX.md @@ -5,3 +5,4 @@ | 001 | 001-reproducible-build-timestamp | Reproducible build timestamp | build, infrastructure, documentation | Fixed `project.build.outputTimestamp` literal in the parent POM, inherited by all child projects, maintained by `release.sh` — so a third party can rebuild any Open Elements Java artifact byte-identically | #3 | done | | 002 | 002-pinned-line-endings | Pinned line endings | build, infrastructure, documentation | `.gitattributes` forcing LF (CRLF for `*.bat`/`*.cmd`), a matching `.editorconfig` aligned with Google Java Format, and an inherited Spotless `lineEndings=UNIX` — so a checkout produces the same text bytes on every platform | #4 | done | | 003 | 003-openapi-stack-versions | OpenAPI stack versions | build, api, documentation | Import `springdoc-openapi-bom` and `swagger-bom` and manage `org.webjars:swagger-ui`, so the coupled OpenAPI stack resolves uniformly in every consumer instead of splitting into a `NoSuchMethodError` | #6 | done | +| 004 | 004-spring-boot-4 | Spring Boot 4 | build, dependencies, documentation | `java-parent` 2.0.0 manages Spring Boot 4.1.2 with a matching OpenAPI stack (springdoc 3.2.0, Swagger 2.2.55, Swagger UI 5.34.0), jspecify 1.0.1 and a Java 25 baseline; 1.x stays the Boot 3.5 line | #8 | done | diff --git a/pom.xml b/pom.xml index 487d847..6fea9ca 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.open-elements java-parent - 1.4.0-SNAPSHOT + 2.0.0-SNAPSHOT pom Java Parent @@ -48,8 +48,8 @@ use the Git-Commit-Time manifest entry written by the full-build profile. --> 2026-09-10T17:19:06Z - 21 - 21 + 25 + 25 3.9.11 - 21 + 25 - 3.5.14 + + 4.1.2 2.0.5 - 1.0.0 + + 1.0.1 - 2.8.17 - 2.2.47 - 5.32.2 + 3.2.0 + 2.2.55 + 5.34.0