Upgrading Keycloak keeps your custom attributes. Rebuilding the realm deletes them.

Upgrading Keycloak keeps your custom attributes. Rebuilding the realm deletes them.

Keycloak 24 made the declarative user profile the default, and the widely repeated warning is that custom user attributes go invisible when you cross that boundary. We tested it, and the warning is aimed at the wrong thing.

A realm created on 23.0.7 with users carrying department and employeeId, upgraded in place to 24.0.5 and on to 26.7.1, returned those attributes from the admin API at every hop, unchanged. Six USER_ATTRIBUTE rows before, six after, six at the end. The in-place upgrade sets that realm's unmanagedAttributePolicy to ENABLED, and everything keeps working.

So upgrading does not hide your custom attributes. Creating a realm on a new version does - and it tells you the write succeeded.

The silent discard

On the same 26.7.1 server, we created a new realm and posted the identical user:

POST /admin/realms/born26/users   →  HTTP 201 select count(*) from user_attribute where user_id = ...  →  0 GET  /admin/realms/born26/users?username=...  →  "attributes": null

The request succeeded with a 201. The attributes were never written, and nothing warned us. A realm created on 24 or later has no unmanagedAttributePolicy, and with it unset the admin API accepts custom attributes and drops them on the floor.

The same policy governs both sides, independently. With it unset, rows that reach the database by another route (we inserted two directly into Postgres) are hidden: the admin API still answers attributes: null. Setting the policy to ENABLED makes both write and read work. But it does not recover what was discarded, because that was never stored. There is nothing to recover.

Export/import is safe - we checked, because we got this wrong first

Our first version of this finding over-generalised: it claimed every path that produces a new realm on a new version - export/import, rebuilds, disaster recovery - loses attributes. That was wrong, and we would have told a customer otherwise.

We exported a 23.0.7 realm and imported it into 24.0.5, 25.0.6, 26.0.0 and 26.7.1. At every target: all six rows survived, the admin API returned them, and the realm still accepted a new unmanaged attribute (204, row count 6 → 7). The import sets unmanagedAttributePolicy=ENABLED - exactly as the in-place migration does, because an imported realm is treated as a legacy realm rather than a new one.

The dangerous path is narrower than we first claimed, and it is worth naming precisely: a realm created through the admin API on 24 or later. Not the upgrade. Not the import.

Our own confession

We nearly published the opposite of all this. Our large test realms were built on 26.0.0 and showed exactly the attributes: null symptom the folklore predicts, and we had written it down as an upgrade regression before checking where the realm came from. The variable was the realm's birth version. This is the second time in this lab a mechanism was correctly identified and then over-generalised into advice, and both times the fix was to stop inferring from one tested path to a class of untested ones.

One query tells you which kind of realm you have

GET /admin/realms/{realm}/users/profile   →   .unmanagedAttributePolicy

If it is absent, your custom attributes are not being stored.

And verify by writing one and reading it back, not by reading an existing user. A read-only check cannot distinguish "hidden" from "never stored", and it is the write path that loses data. HTTP 201 is not evidence that the attributes were saved; count the rows.

What we did not test: tokens/mappers reading unmanaged attributes, partial export through the admin API (which excludes users by default), the ADMIN_VIEW / ADMIN_EDIT policy values, and any realm larger than 3 users.

Keycloak Advisory Watch - one email per advisory batch, within 72 hours of publication, listing the patched versions per maintained minor line. Double opt-in, no tracking pixels, no click tracking, public archive. Subscribe.

Self-hosted Keycloak is one of the things we keep patched, upgraded and owned for teams that run it but have nobody to run it. Talk to us about your project.

Sources: 2026-08-25-s15-unmanaged-attributes, 2026-08-26-runC-realm-export-import-23-to-24.

Next
Next

How long does a Keycloak migration take? It depends on a number that's stale by design.