Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/snapshot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 <server id="central-portal-snapshots">
# whose credentials come from the env vars below. The id must match
Expand Down
2 changes: 1 addition & 1 deletion .sdkmanrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
java=21
java=25
34 changes: 27 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,37 @@ boilerplate into every project.
<parent>
<groupId>com.open-elements</groupId>
<artifactId>java-parent</artifactId>
<version>1.0.0</version>
<version>2.0.0</version>
</parent>
```

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
Expand All @@ -42,10 +61,10 @@ installation is required.
Import-scoped BOMs so child projects can declare these dependencies **without a
`<version>`**:

- **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
Expand Down Expand Up @@ -99,7 +118,8 @@ org/springdoc/springdoc-openapi/<version>/springdoc-openapi-<version>.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

Expand All @@ -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).
Expand Down
70 changes: 70 additions & 0 deletions docs/specs/004-spring-boot-4/behaviors.md
Original file line number Diff line number Diff line change
@@ -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 `<version>`
- **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
`<version>`
- **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 `<version>`
- **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 `<version>`
- **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
120 changes: 120 additions & 0 deletions docs/specs/004-spring-boot-4/design.md
Original file line number Diff line number Diff line change
@@ -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 `<parent><version>` 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** | `<swagger-api.version>` in `springdoc-openapi-3.2.0.pom` |
| `swagger-ui.version` | 5.32.2 | **5.34.0** | `<swagger-ui.version>` 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 `<version>`) 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.
84 changes: 84 additions & 0 deletions docs/specs/004-spring-boot-4/steps.md
Original file line number Diff line number Diff line change
@@ -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 `<swagger-api.version>` / `<swagger-ui.version>` 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.
1 change: 1 addition & 0 deletions docs/specs/INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Loading
Loading