Keycloak public-API removals, by upgrade hop
- Condition
- Standing report, not a rehearsal of a version pair: the measured public-API removal surface per upgrade hop, cited by four records and the custom-SPI post
Results
"Added" is included so the removal count is read against a surface that is growing, not shrinking.
Patch hops
| Hop | Removed | Added |
|---|---|---|
| 26.7.0 → 26.7.1 | 0 | 9 |
| 26.7.1 → 26.7.2 | 0 | 3 |
| 26.7.2 → 26.7.3 | 4 | 12 |
Minor hops, inside major 26
| Hop | Removed | Added |
|---|---|---|
| 26.0.0 → 26.1.0 | 46 | 228 |
| 26.1.0 → 26.2.0 | 17 | 260 |
| 26.2.0 → 26.3.0 | 137 | 212 |
| 26.6.0 → 26.7.0 | 78 | 374 |
Major boundaries
| Hop | Removed | Added |
|---|---|---|
| 21.1.2 → 22.0.5 | 277 | 286 |
| 22.0.5 → 23.0.7 | 61 | 314 |
| 23.0.7 → 24.0.5 | 175 | 522 |
| 24.0.5 → 25.0.6 | 128 | 680 |
| 25.0.6 → 26.0.0 | 68 | 300 |
Spans
| Span | Removed | Added |
|---|---|---|
| 26.0.0 → 26.7.1 (within one major) | 400 | 2,310 |
| 21.1.2 → 26.7.1 (five majors, direct) | 907 | 4,210 |
Findings
1. Patch releases are nearly clean — until a security rework reaches in
26.7.0 → 26.7.1 and 26.7.1 → 26.7.2 removed nothing. Every minor hop
measured removed between 17 and 137 signatures. The first patch hop to remove
anything is 26.7.2 → 26.7.3, which removed 4 — and all four are security-
rework casualties, not routine cleanup:
AdminRoles.ALL_ROLES,ALL_QUERY_ROLES,ALL_REALM_ROLES— removed by the fine-grained-permissions (FGAP v2) admin-RBAC hardening in the 26.7.3 advisory batch (CVE-2026-16105 and the other FGAP fixes). A custom provider that referenced these "everything" role constants is exactly the thing that compiles against 26.7.2 and breaks against 26.7.3.RepresentationToModel.OIDC— an internal constant, in thekeycloak-server-spi-privateartifact that carries no compatibility promise.
So "patch = safe" still holds for routine patch releases, but a patch that bundles a security batch can carry removals. Treat the advisory batch as the unit of breakage risk, not the version-number increment.
2. The major/minor boundary does not predict breakage
26.2.0 → 26.3.0 removed 137 — in the same band as the 23.0.7 → 24.0.5
major boundary (175) and roughly twice 25.0.6 → 26.0.0 (68). Staying inside a
major is not protection. Only the patch level is.
3. Staying inside one major is not a small distance
26.0.0 → 26.7.1 removed 400 signatures — more than any single major
boundary measured, and 44% of the total across five majors (907).
4. This sharpens the corrected ledger thesis
Entry 01 now says the support window is one major wide and coverage of minor lines varies per batch. Combine that with the numbers above:
When an advisory batch tells you the fix is in 26.4.14 and you are on 26.0.x, that is not a patch. It is four minor hops, and on this evidence those hops remove on the order of 200 public API signatures. If you run custom extensions, the "just patch" path and the "upgrade project" path can be the same path.
That is a claim no vendor makes and no release note states, and it is measured rather than argued.
5. The model-artifact relocation is where the widened diff bites
The 2026-08-25 pass said 23.0.7 → 24.0.5 removed 145 and 24.0.5 → 25.0.6
removed 94. With the model artifacts included those are 175 and 128 —
an extra 64 signatures across two hops, all of it in the artifacts a custom
user-storage provider compiles against:
23.0.7 → 24.0.5is the split:keycloak-model-storageappears whilekeycloak-model-legacyis still published. The types that vanish are the legacy-store scaffolding —org.keycloak.storage.LegacyStoreManagers,LegacyStoreMigrateRepresentationEvent,LegacyStoreSyncEvent,org.keycloak.models.locking.GlobalLockProviderand its SPI.24.0.5 → 25.0.6is the deletion:keycloak-model-legacystops being published entirely. The last legacy types go —org.keycloak.models.LegacyRealmModel,org.keycloak.credential.LegacyUserCredentialManager,org.keycloak.http.HttpCookie.
The reverse direction is not a removal but a relocation: a user-storage provider
built against ≤23 references classes whose FQN still exists in 25+, but now ships
in keycloak-model-storage. That survives a source-compatible rebuild
(build-spi.sh needs the new artifact in the pom — see the operational notes),
but a jar compiled against the old coordinates is the "which artifact, not which
API" trap.
For the brittle (removed-API) side of that question — whether such a provider fails at load vs at first use — see the provider-types record; that is a runtime test, not a signature diff.
Caveats
- Signature-level, not behavioural. A method that survives with changed semantics is invisible here and is the harder class of breakage.
- Generic type parameters are stripped before comparison, so a signature differing only in generics is treated as unchanged.
- Counts the public and protected surface only.
21.1.2 → 26.7.1direct (907) is less than the sum of the hops (1,109) because the direct diff counts net removals — anything removed and later re-added does not appear.- The model artifacts are optional:
keycloak-model-legacyis not published after 24.0.5 andkeycloak-model-storagenot before 24.0.5.spi-api-diff.shskips an absent artifact rather than failing, so each hop diffs exactly the artifacts that exist on both ends plus any that appear or vanish in between — the appearance/vanish itself being part of the signal. - Compares published Maven artifacts, not the runtime classpath of the container image.