Upgrading Keycloak across major versions is one of those tasks that looks simple on paper — download the new release, start it up, let Liquibase handle the database — but reliably creates production incidents when done without preparation. Between versions 21 and 26, Keycloak introduced several breaking changes that affect clustering, theming, SPIs, and configuration format.

This guide covers what actually breaks at each version boundary and how to handle it.

The Golden Rule: No Version Skipping

Keycloak’s database migration uses Liquibase changesets that execute sequentially. Version 23’s schema changes assume version 22’s schema is in place. If you try to jump from 21 to 26 directly, Liquibase will either fail outright or corrupt your data.

The upgrade path is: 21 → 22 → 23 → 24 → 25 → 26

Each hop requires starting Keycloak, letting the migration complete, verifying everything works, and then moving to the next version. Yes, it’s tedious. No, there’s no shortcut.

Pre-Upgrade Checklist

Before touching anything:

# 1. Back up the database
pg_dump -Fc -h localhost -U keycloak keycloak > keycloak-v21-backup.dump

# 2. Export realm configuration (safety net)
/opt/keycloak/bin/kc.sh export --dir /backup/realm-export --users realm_file

# 3. Document current startup command and environment variables
ps aux | grep keycloak
env | grep KC_

# 4. List installed custom providers
ls /opt/keycloak/providers/

Keep the backup and export for every version step. If version 24’s migration corrupts something, you want to restore to the post-23 state, not start from 21 again.

Version 21 → 22: Quarkus Stabilization

If you’re on the WildFly distribution (Keycloak < 20), you must migrate to Quarkus first. Version 22 is fully Quarkus-based and the WildFly distribution is gone.

Breaking Changes

  • Configuration format: standalone.xml is replaced by keycloak.conf and CLI arguments. Map your WildFly datasource and SPI configurations to KC_* environment variables.
  • Custom SPI packaging: WildFly-style EAR/WAR deployments become simple JAR files in /opt/keycloak/providers/.
  • Deprecated Admin Console: The old admin console still works but shows deprecation warnings.

Migration Steps

# Convert old standalone.xml settings to new format
# Old (WildFly): datasource in standalone.xml
# New (Quarkus):
KC_DB=postgres
KC_DB_URL=jdbc:postgresql://localhost:5432/keycloak
KC_DB_USERNAME=keycloak
KC_DB_PASSWORD=secret

# Start with build optimization
/opt/keycloak/bin/kc.sh build
/opt/keycloak/bin/kc.sh start --optimized

Version 22 → 23: New Admin Console

Breaking Changes

  • Admin Console v2 becomes default: The old admin console is removed. If you had custom admin console extensions or plugins, they need rewriting for the React-based v2 console.
  • Deprecated API endpoints: Several Admin REST API endpoints for legacy features are removed.
  • Hostname configuration: The hostname provider configuration changes. KC_HOSTNAME_STRICT behavior is tightened.

Watch Out For

If your monitoring or automation scripts hit the old admin console endpoints, they’ll break. Update to use the Admin REST API v2 endpoints:

# Old endpoint (may be removed)
curl https://keycloak.example.com/auth/admin/realms/master/...

# New standard endpoint
curl https://keycloak.example.com/admin/realms/master/...

Note the path change: /auth/admin//admin/. The /auth prefix was removed as default in the Quarkus distribution.

Version 23 → 24: Persistent User Sessions

Breaking Changes

  • Persistent user sessions: User sessions can now survive restarts by persisting to the database. This changes session behavior — sessions that previously died on restart now persist.
  • Client policy updates: Client policies for FAPI and security profiles are restructured.
  • User Profile enabled by default: The declarative user profile feature is enabled by default, which changes registration and profile update forms.

Configuration Change

# If you DON'T want persistent sessions (maintain old behavior):
spi-user-sessions-infinispan-offline-session-cache-entry-lifespan-override=0

# If you DO want persistent sessions (recommended):
# No change needed, it's the new default

User Profile Impact

With User Profile enabled by default, the registration and profile pages enforce the configured user attributes. If your custom theme relies on hardcoded form fields, they may disappear or change order. Test your registration flow thoroughly.

Version 24 → 25: Organizational Support

Breaking Changes

  • Organizations feature (preview): Multi-tenancy support is introduced as a preview feature.
  • Lightweight access tokens: Access tokens are now lightweight by default — they contain minimal claims. Full user info requires the UserInfo endpoint or explicit token configuration.
  • Deprecated features removed: Several deprecated authenticators and form actions are removed.

Lightweight Access Token Impact

This is the change most likely to break applications. If your APIs parse access tokens for user claims (email, name, groups), those claims are no longer present by default.

Fix: Configure client scopes to include the needed claims:

  1. Go to Client → Client Scopes → Assigned Scopes
  2. Ensure profile, email, and any custom scopes are assigned
  3. On each scope’s mappers, set Add to access token to ON

Or configure the client to use full tokens:

{
  "attributes": {
    "use.lightweight.access.token.enabled": "false"
  }
}

Version 25 → 26: The Big One (Infinispan Protostream)

Version 26 has the most impactful infrastructure change since the Quarkus migration.

Infinispan Cache Serialization Switch

Keycloak 26 switches from JBoss Marshalling to Infinispan Protostream for cache serialization. This means:

  • Rolling upgrades don’t work: Old nodes (v25) and new nodes (v26) cannot deserialize each other’s cache entries. You’ll get ClassCastException or MarshallingException in the logs.
  • Solution: Do a blue-green deployment or accept a brief session flush.
# Blue-green approach:
# 1. Deploy v26 cluster alongside v25 cluster
# 2. Point load balancer to v26
# 3. Users re-authenticate (sessions don't transfer)
# 4. Shut down v25 cluster

# Alternative: flush sessions before upgrade
# 1. Clear the infinispan caches via JMX or CLI
# 2. Stop all v25 nodes
# 3. Start v26 nodes
# Users will need to re-login

Login Theme v2

Keycloak 26 defaults to the v2 login theme (PatternFly-based). Custom themes that extend keycloak must now extend keycloak.v2:

# theme.properties (old)
parent=keycloak
import=common/keycloak

# theme.properties (new)
parent=keycloak.v2

Template file paths also change. If you’ve overridden individual FTL files, check that the template names haven’t changed between versions.

Bootstrap Admin Recovery

Keycloak 26 adds a bootstrap admin mechanism. If all admin accounts are locked out:

KC_BOOTSTRAP_ADMIN_USERNAME=temp-admin \
KC_BOOTSTRAP_ADMIN_PASSWORD=temp-password \
/opt/keycloak/bin/kc.sh start

This creates a temporary admin account for recovery. Remove it after resetting the real admin credentials.

Step-by-Step Upgrade Process

For each version hop (repeat 5 times for 21→26):

# 1. Stop current Keycloak
systemctl stop keycloak

# 2. Back up database
pg_dump -Fc keycloak > keycloak-pre-vXX-upgrade.dump

# 3. Replace Keycloak binaries
tar xzf keycloak-XX.0.0.tar.gz
cp -r /opt/keycloak/conf/* /opt/keycloak-XX.0.0/conf/
cp /opt/keycloak/providers/*.jar /opt/keycloak-XX.0.0/providers/
ln -sfn /opt/keycloak-XX.0.0 /opt/keycloak

# 4. Rebuild (Quarkus optimization)
/opt/keycloak/bin/kc.sh build

# 5. Start and watch the logs
/opt/keycloak/bin/kc.sh start | tee /var/log/keycloak-upgrade-vXX.log

# 6. Verify
curl -s https://keycloak.example.com/health/ready
# Should return {"status":"UP"}

# 7. Smoke test
# - Admin console login
# - User login via SAML/OIDC client
# - Check custom SPIs loaded

Monitoring During Upgrade

Watch these metrics during and after each version upgrade:

  • Login success rate: Drop indicates broken authentication flows or missing claims
  • Admin API response time: Spike suggests database migration is still running
  • Infinispan cache miss rate: High rate after v26 upgrade indicates the Protostream switch is working (cache is rebuilding)
  • Custom SPI errors: Check for ClassNotFoundException or NoSuchMethodError in logs — indicates SPI API changes
# Quick health check script
while true; do
  HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" https://keycloak.example.com/health/ready)
  echo "$(date): Health status: $HTTP_CODE"
  sleep 5
done

Post-Upgrade Cleanup

After reaching version 26 and confirming everything works:

  1. Remove old Keycloak distribution directories
  2. Update your CI/CD pipelines and Docker images to build from v26
  3. Review and update custom SPIs for deprecated API usage
  4. Enable new features: Organizations (preview), improved WebAuthn, updated Infinispan configuration
  5. Update operational runbooks with the new admin console URLs and CLI commands

Keycloak’s rapid release cycle means you’ll be doing this again in 6-12 months. Consider automating the upgrade process with a staging environment that runs the full sequence before production.