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/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. 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