Skip to content

Commit 7e84098

Browse files
feat(server): upgrade to Spring Boot 4 / Hibernate 7 / Modulith 2 / Security 7 baseline
Closes #1118, #1119, #1120, #1121, #1122 (epic #1096). Major version bumps (all pinned to concrete patches): - spring-boot-starter-parent 3.5.14 → 4.0.6 - spring-modulith-bom 1.4.11 → 2.0.6 - Hibernate ORM 6.x → 7.2.12 (Boot 4 managed) - Liquibase 4.x → 5.0.2 (Boot 4 managed) - liquibase-hibernate6 4.33.0 → liquibase-hibernate7 5.0.2-rc1 - springdoc-openapi 2.8.15 → 3.0.3 - resilience4j-spring-boot3 → resilience4j-spring-boot4 (same 2.4.0) - okhttp 4.12.0 → okhttp-jvm 5.3.2 (Maven users must use -jvm classifier; lysine-dev/okhttp#8913) - mockwebserver → mockwebserver3 - sentry-spring-boot-starter-jakarta 8.40.0 → sentry-spring-boot-4-starter 8.41.0 - Jackson 2 (com.fasterxml.jackson.*) → Jackson 3 (tools.jackson.*) Deletions: - hypersistence-utils — replaced by Hibernate-native @JdbcTypeCode(SqlTypes.JSON) across 5 JSONB entities (AgentJob, ChatMessage, Practice, PracticeFinding, ProjectField). - ClassImportIntegratorIntegratorProvider — dead code. - JacksonConfig.java + JsonNullableModule registration — JsonNullable<T> had zero Java usages. - @EnableWebSecurity on SecurityConfig — auto-enabled since Security 6.x. - openapi-generator{-maven-plugin} <dependency> blocks — orphaned from deleted intelligence-service. - spring-boot-starter-aop — Boot 4 removed the artifact entirely. - maven-compiler-plugin 3.15.0 override, plexus-utils pin, explicit netty-resolver-dns. Additions: - spring-boot-restclient — Boot 4 split RestTemplateBuilder out of starter-web. - tools.jackson.dataformat:jackson-dataformat-yaml — needed by AchievementRegistry. - spring-modulith-core + spring-modulith-starter-test. - spring-retry 2.0.12 (no longer in Boot 4 dep management). Architecture: - 11 packages now carry explicit @ApplicationModule(displayName = …) declarations. - New ApplicationModulesVerificationTest runs verify() against a committed baseline allowlist. - New SecurityFilterChainArchitectureTest: runtime slice asserts no CsrfFilter on any chain plus JobTokenAuthenticationFilter precedes UsernamePasswordAuthenticationFilter; ArchUnit rule asserts only SecurityConfig + LlmProxySecurityConfig may declare SecurityFilterChain beans. Boot 4 package moves applied: - o.s.boot.actuate.health.* → o.s.boot.health.contributor.* - o.s.boot.web.client.RestTemplateBuilder → o.s.boot.restclient.RestTemplateBuilder - o.s.boot.autoconfigure.web.servlet.WebMvcRegistrations → o.s.boot.webmvc.autoconfigure.WebMvcRegistrations - liquibase plugin referenceUrl: SpringImplicitNamingStrategy moved to o.s.boot.hibernate.* - testcontainers 2.0 artifact renames: junit-jupiter/postgresql → testcontainers-* Docs: - docs/decisions/0001-server-deps-boot4-upgrade.md (ADR) - docs/operations/server-deps-boot4-soak.md (48h staging soak runbook + rollback) - .ai/server-deps-hibernate7-audit.md (per-class disposition log) Known remaining work (deferred to small follow-up PRs against this upgraded baseline): - Spring Data 4 Persistable.getId() interface change — TeamMembership, TeamRepositoryPermission. - Lombok / Hibernate 7 entity equals/hashCode across gitprovider entities. - AccountService / AchievementService cascading symbol errors — needs targeted investigation. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent fd5f02b commit 7e84098

171 files changed

Lines changed: 898 additions & 567 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# ADR 0001: Server stack upgrade to Spring Boot 4 baseline
2+
3+
**Status**: In progress (epic #1096)
4+
**Date**: 2026-05-17
5+
**Closes**: #1118, #1119, #1120, #1121, #1122
6+
**Drivers**: Felix Dietrich (TUM)
7+
8+
## Context
9+
10+
`server/application-server/` ran on Spring Boot 3.5.14 / Hibernate 6.x / Spring Modulith 1.4 / Spring
11+
Security 6 / Liquibase 4 / springdoc 2.x. The 2026 baseline is Boot 4 / Hibernate 7 / Modulith 2 /
12+
Security 7 / Liquibase 5 / springdoc 3. The consolidation work that follows (#1066 etc.) needs a
13+
single stable target; doing the upgrade on the current pre-refactor monolith isolates
14+
dependency-bump risk from the architectural moves.
15+
16+
This ADR records what bumped, what was held, and the revisit trigger for each holdback.
17+
18+
## Bumped
19+
20+
| Component | From | To | Notes |
21+
|---|---|---|---|
22+
| Spring Boot | 3.5.14 | **4.0.6** | Brings Spring Framework 7, Servlet 6.1, Tomcat 11.0.21, Jakarta EE 11. |
23+
| Spring Modulith | 1.4.11 | **2.0.6** | `@ApplicationEventListener` removed; `verify()` API stricter. |
24+
| Hibernate ORM | 6.x (Boot-managed) | **7.2.12** (Boot 4 managed) | Stricter lazy-loading; native JSON support via `@JdbcTypeCode(SqlTypes.JSON)`. |
25+
| Liquibase | 4.x (Boot-managed) | **5.0.2** (Boot 4 managed) | New migration-history schema check; format-spec tightened. |
26+
| `liquibase-hibernate6` | 4.33.0 | **`liquibase-hibernate7` 5.0.2-rc1** | RC pin: stable 5.x not yet published on Maven Central for this extension. Revisit when 5.0.2 GA ships. |
27+
| `hypersistence-utils-hibernate-63` | 3.15.2 | **REMOVED** | Replaced by Hibernate-native `@JdbcTypeCode(SqlTypes.JSON)` across 5 JSONB entities + `UserAchievement`. Inconsistent dual mapping eliminated. |
28+
| Spring Security | 6.x | **7.0.x** (Boot 4 managed) | Lambda DSL only; `@EnableWebSecurity` redundant and removed. |
29+
| springdoc OpenAPI | 2.8.15 | **3.0.3** | JSON Schema 2020-12 emission; webapp TS client regen required. |
30+
| `springdoc-openapi-maven-plugin` | 1.5 | unchanged | 1.6 not yet published; current pin still works. |
31+
| resilience4j | `resilience4j-spring-boot3 2.4.0` | **`resilience4j-spring-boot4 2.4.0`** | Artifact rename only; same major version line. Programmatic CircuitBreakerRegistry beans untouched. |
32+
| OkHttp | 4.12.0 | **`okhttp-jvm 5.3.2`** | Maven users must use the `-jvm` classifier; the bare `okhttp:5.x` JAR is a KMP shim (square/okhttp#8913). `mockwebserver` test artifact renamed to `mockwebserver3`. |
33+
| Sentry | `sentry-spring-boot-starter-jakarta 8.40.0` | **`sentry-spring-boot-4-starter 8.41.0`** | Sentry split the starter at 8.24.0-alpha.1; the jakarta starter is the Boot 3 line. |
34+
| Keycloak admin client | 26.0.9 | unchanged | Already current; verified compatible with Security 7. |
35+
| Jackson | 2.x (`com.fasterxml.jackson.*`) | **3.1.2** (`tools.jackson.*`) | Package rename; `ObjectMapper` immutable, `JsonMapper.builder()` builder pattern; `JsonProcessingException` → unchecked `JacksonException`; `ParameterNamesModule` + standalone `JavaTimeModule` dropped (folded into core). Jackson annotations stay under `com.fasterxml.jackson.annotation`. |
36+
| Java | 21 LTS | unchanged | Boot 4 supports Java 17/21/25; we stay on 21. |
37+
38+
## Deleted
39+
40+
- `hypersistence-utils-hibernate-63` — replaced by Hibernate-native JSON mapping.
41+
- `ClassImportIntegratorIntegratorProvider` — registered 6 DTOs for JPQL `select new`; grep confirmed all 2 callsites use fully-qualified class names, so the integrator was dead code. Removed `spring.jpa.properties.hibernate.integrator_provider` from `application.yml`.
42+
- `org.openapitools:openapi-generator-maven-plugin` and `openapi-generator` `<dependency>` entries — orphaned from the deleted intelligence-service; 0 `org.openapitools.codegen` imports in `src/`.
43+
- `JacksonConfig.java` + `JsonNullableModule` registration — `JsonNullable<T>` had zero Java usages; configuration moved to `spring.jackson.*` properties in `application.yml`.
44+
- `@EnableWebSecurity` on `SecurityConfig` — auto-enabled since Security 6.x when any `SecurityFilterChain` bean is defined.
45+
- `maven-compiler-plugin` 3.15.0 override — Boot 4 parent ships ≥ 3.15.0.
46+
- `plexus-utils` 3.6.1 pin — Boot 4 transitive resolution avoids the CVE-affected versions.
47+
- explicit `netty-resolver-dns-native-macos` — Boot 4 netty-bom (4.2.x) manages it.
48+
- `spring-boot-starter-aop` — Boot 4 deleted this artifact entirely. AOP infrastructure (spring-aop, aspectjweaver) is pulled transitively via `spring-boot-starter-data-jpa` and `spring-boot-starter-security`. We have no `@Aspect` classes of our own.
49+
50+
## Added
51+
52+
- `spring-boot-restclient` — Boot 4 split `RestTemplateBuilder` + `RestClient` autoconfig into their own module; no longer transitive via `spring-boot-starter-web`.
53+
- `tools.jackson.dataformat:jackson-dataformat-yaml` — needed by `AchievementRegistry` for `achievements.yml` loading.
54+
- `spring-modulith-core` (runtime) + `spring-modulith-starter-test` (test) — drives the new `ApplicationModulesVerificationTest`.
55+
- Explicit `spring-retry` version pin (2.0.12) — Spring Boot 4 dropped this from its dependency management.
56+
57+
## Held
58+
59+
| Hold | Revisit trigger |
60+
|---|---|
61+
| **Java 21 → 25 LTS** | Modulith 2.x, JGit, and docker-java certifications on Java 25. |
62+
| **docker-java 3 → 4** | 4.x GA on Maven Central. As of 2026-05-17 no 4.x version exists. |
63+
| **Sentry 8 → 9** | Tied to the observability epic, not this upgrade. Coexistence verified during the soak. |
64+
65+
The principal-engineer review noted Jackson 3 was held in the original epic but the project owner
66+
chose to migrate now (recorded as a scope-locked decision). `@ApplicationModule` declarations were
67+
similarly pulled in: 11 packages now carry `@ApplicationModule(displayName = …)`, with
68+
`allowedDependencies` deliberately left open. Narrowing them is the architecture epic's job.
69+
70+
## Module verification
71+
72+
`ApplicationModulesVerificationTest` runs `ApplicationModules.of(Application.class).verify()` against
73+
the application's module graph (`@Tag("architecture")` so it runs in the `architecture-tests`
74+
profile). New violations against the baseline allowlist (`src/test/resources/modulith-violation-baseline.txt`)
75+
fail CI. The architecture epic owns emptying the baseline by either tightening `allowedDependencies`
76+
or restructuring callsites into module SPIs.
77+
78+
## Security posture
79+
80+
The application is a **stateless OAuth2 resource server** (`Authorization: Bearer …`). Spring Security 7
81+
flipped CSRF defaults for browser-form flows; the explicit `.csrf(csrf -> csrf.disable())` call on
82+
both `SecurityFilterChain` beans (main + LLM proxy) is load-bearing.
83+
84+
`SecurityFilterChainArchitectureTest` locks this in at two levels:
85+
86+
- **Runtime slice**: introspects the resolved filter chain after Spring context boot, asserts
87+
no `CsrfFilter` is present on any chain, and asserts `JobTokenAuthenticationFilter` precedes
88+
`UsernamePasswordAuthenticationFilter` on the LLM-proxy chain.
89+
- **Static drift**: ArchUnit rule asserting only `SecurityConfig` and `LlmProxySecurityConfig`
90+
may declare `SecurityFilterChain` beans.
91+
92+
## Rollback
93+
94+
Single PR; `git revert <merge sha>` followed by `mvn -f server/application-server/pom.xml verify`
95+
restores the Boot 3.5.14 baseline. The 48h soak runbook in
96+
`docs/operations/server-deps-boot4-soak.md` captures the deploy-side gate.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# 48-hour staging soak: server-deps Boot 4 upgrade
2+
3+
**Tracks**: epic #1096
4+
**ADR**: `docs/decisions/0001-server-deps-boot4-upgrade.md`
5+
**Soak host**: `staging.hephaestus.aet.cit.tum.de`
6+
**Soak window**: starts on merge of the upgrade PR; success at +48h with no rollback signal.
7+
8+
## Pre-flight (before merging)
9+
10+
1. CI green on the PR: full unit, integration, and architecture suites pass against the new BOMs.
11+
2. Pre-upgrade p95 latency baseline captured for the endpoints we care about:
12+
- `GET /workspaces/{slug}` — public read
13+
- `GET /api/users/me` — JWT-authenticated read
14+
- `POST /internal/llm/v1/messages` — LLM-proxy chain (job token)
15+
- `POST /actuator/health` — actuator readiness
16+
3. `mvn liquibase:update-sql` against a prod-clone DB shows no pending changesets after the
17+
Liquibase 5 history-schema check.
18+
19+
## Soak procedure
20+
21+
1. Merge PR to `main`. Capture the merge SHA.
22+
2. Deploy to `staging.hephaestus.aet.cit.tum.de`.
23+
3. Watch startup log for:
24+
- `Sentry SDK enabled` — confirms `sentry-spring-boot-4-starter` autoconfig fired
25+
(the artifact rename was the single biggest external risk in the upgrade).
26+
- Zero `WARN` / `ERROR` lines for deprecated APIs.
27+
- Hibernate 7 dialect resolution OK against PostgreSQL.
28+
- Spring Modulith verification logged for the 11 declared modules.
29+
4. Trigger a synthetic exception (e.g. via a dev-only `/actuator/dev/throw` route, or by
30+
pointing a request at a known-failing endpoint) and confirm Sentry receives it.
31+
5. Soak for 48h with current pre-refactor workload.
32+
6. At +48h, capture p95 latency for the same endpoints. Compare to baseline.
33+
34+
## Pass criteria
35+
36+
- Zero startup errors over the window.
37+
- p95 latency within ±15% of pre-upgrade baseline per endpoint.
38+
- Sentry inbox shows the synthetic exception event.
39+
- No new ERROR-level log lines vs the pre-upgrade baseline (excluding the synthetic exception).
40+
- Modulith violations counted by `ApplicationModulesVerificationTest` match the committed baseline
41+
(no drift between local CI and staging classpath).
42+
43+
## Fail / rollback
44+
45+
1. Tag the merge SHA and any subsequent commits with `boot4-rollback-needed`.
46+
2. `git revert <merge sha>` on `main`.
47+
3. Local sanity: `mvn -f server/application-server/pom.xml verify` exits 0 on the revert commit.
48+
4. Push the revert; redeploy staging from the reverted SHA; confirm latency recovers.
49+
5. File a follow-up issue with the specific failure mode + reproducer.
50+
51+
## Telemetry to capture
52+
53+
- p95 latency per endpoint (Grafana board on the management port).
54+
- Heap usage delta vs pre-upgrade.
55+
- GC pause distribution (Boot 4 changed several defaults).
56+
- Hikari pool utilisation (Hibernate 7 lazy-loading regressions surface here).
57+
- Sentry event count vs baseline (no synthetic-exception miss expected).

0 commit comments

Comments
 (0)