diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/JpaUpdate26_6_0_MigrateRealmDisplayName.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/JpaUpdate26_6_0_MigrateRealmDisplayName.java
new file mode 100644
index 00000000000..1bb48e9b33e
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/JpaUpdate26_6_0_MigrateRealmDisplayName.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2026 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.connections.jpa.updater.liquibase.custom;
+
+import liquibase.database.core.MSSQLDatabase;
+import liquibase.database.core.MySQLDatabase;
+import liquibase.database.core.OracleDatabase;
+import liquibase.database.core.PostgresDatabase;
+import liquibase.exception.CustomChangeException;
+import liquibase.statement.core.RawParameterizedSqlStatement;
+
+/**
+ * Custom SQL change to migrate the displayName from the REALM_ATTRIBUTE table to the REALM table.
+ * See: keycloak#45292
+ *
+ * @author tre2man
+ */
+public class JpaUpdate26_6_0_MigrateRealmDisplayName extends CustomKeycloakTask {
+
+ @Override
+ protected void generateStatementsImpl() throws CustomChangeException {
+ String realmTable = getTableName("REALM");
+ String realmAttributeTable = getTableName("REALM_ATTRIBUTE");
+
+ if (database instanceof PostgresDatabase) {
+ generateUpdateQueryForPostgresSQL(realmTable, realmAttributeTable);
+ } else if (database instanceof MySQLDatabase) {
+ generateUpdateQueryForMySQL(realmTable, realmAttributeTable);
+ } else if (database instanceof MSSQLDatabase) {
+ generateUpdateQueryForMsSQL(realmTable, realmAttributeTable);
+ } else if (database instanceof OracleDatabase) {
+ generateUpdateQueryForOracleSQL(realmTable, realmAttributeTable);
+ } else {
+ generateUpdateQueryUsingStandardSQL(realmTable, realmAttributeTable);
+ }
+
+ generateDelete(realmAttributeTable);
+ }
+
+ private void generateUpdateQueryForPostgresSQL(String realmTable, String realmAttributeTable) {
+ statements.add(new RawParameterizedSqlStatement("""
+ UPDATE %s r
+ SET display_name = ra.value
+ FROM %s ra
+ WHERE ra.realm_id = r.id and ra.name = 'displayName'
+ """
+ .formatted(realmTable, realmAttributeTable)));
+ }
+
+ private void generateUpdateQueryForMySQL(String realmTable, String realmAttributeTable) {
+ statements.add(new RawParameterizedSqlStatement("""
+ UPDATE %s r
+ JOIN %s ra ON r.id = ra.realm_id
+ SET r.display_name = ra.value
+ WHERE ra.name = 'displayName'
+ """
+ .formatted(realmTable, realmAttributeTable)));
+ }
+
+ private void generateUpdateQueryForMsSQL(String realmTable, String realmAttributeTable) {
+ statements.add(new RawParameterizedSqlStatement("""
+ UPDATE r
+ SET display_name = ra.value
+ FROM %s r
+ JOIN %s ra ON r.id = ra.realm_id
+ WHERE ra.name = 'displayName'
+ """
+ .formatted(realmTable, realmAttributeTable)));
+ }
+
+ private void generateUpdateQueryForOracleSQL(String realmTable, String realmAttributeTable) {
+ statements.add(new RawParameterizedSqlStatement("""
+ MERGE INTO %s r
+ USING (
+ SELECT realm_id, value
+ FROM %s
+ WHERE name = 'displayName'
+ ) ra
+ ON (ra.realm_id = r.id)
+ WHEN MATCHED THEN
+ UPDATE SET r.display_name = ra.value
+ """
+ .formatted(realmTable, realmAttributeTable)));
+ }
+
+ private void generateUpdateQueryUsingStandardSQL(String realmTable, String realmAttributeTable) {
+ statements.add(new RawParameterizedSqlStatement("""
+ UPDATE %s r
+ SET r.display_name = (
+ SELECT ra.value
+ FROM %s ra
+ WHERE ra.realm_id = r.id AND ra.name = 'displayName'
+ )
+ WHERE EXISTS (
+ SELECT 1
+ FROM %s ra
+ WHERE ra.realm_id = r.id AND ra.name = 'displayName'
+ )
+ """
+ .formatted(realmTable, realmAttributeTable, realmAttributeTable)));
+ }
+
+ private void generateDelete(String realmAttributeTable) {
+ statements.add(new RawParameterizedSqlStatement(
+ "DELETE FROM %s WHERE name = 'displayName'".formatted(realmAttributeTable)));
+ }
+
+ @Override
+ protected String getTaskId() {
+ return "Migrate displayName from REALM_ATTRIBUTE to REALM.DISPLAY_NAME column";
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index f00c5d18cab..1c399653573 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -144,12 +144,13 @@ public class RealmAdapter implements StorageProviderRealmModel, JpaModel attributes = new LinkedList<>();
@@ -500,6 +503,14 @@ public class RealmEntity {
this.emailTheme = emailTheme;
}
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ public void setDisplayName(String displayName) {
+ this.displayName = displayName;
+ }
+
public int getNotBefore() {
return notBefore;
}
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-26.6.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-26.6.0.xml
index 6fb98db1186..d8dab91ce8b 100644
--- a/model/jpa/src/main/resources/META-INF/jpa-changelog-26.6.0.xml
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-26.6.0.xml
@@ -97,4 +97,14 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/base/src/test/java/org/keycloak/tests/admin/realm/RealmAttributesTest.java b/tests/base/src/test/java/org/keycloak/tests/admin/realm/RealmAttributesTest.java
index 5dcba84f6f3..a7008c85a71 100644
--- a/tests/base/src/test/java/org/keycloak/tests/admin/realm/RealmAttributesTest.java
+++ b/tests/base/src/test/java/org/keycloak/tests/admin/realm/RealmAttributesTest.java
@@ -268,4 +268,28 @@ public class RealmAttributesTest extends AbstractRealmTest {
adminClient.realm(realmName).remove();
}
}
+
+ @Test
+ public void testDisplayNameNotRemovableViaRemoveAttribute() {
+ String realmName = "testDisplayNameNotRemovable";
+ RealmRepresentation rep = new RealmRepresentation();
+ rep.setRealm(realmName);
+ rep.setDisplayName("Test Display Name");
+
+ adminClient.realms().create(rep);
+
+ try {
+ runOnServer.run(session -> {
+ RealmModel realm = session.realms().getRealmByName(realmName);
+
+ assertEquals("Test Display Name", realm.getDisplayName());
+ realm.removeAttribute("displayName");
+ assertEquals("Test Display Name", realm.getDisplayName());
+ realm.setDisplayName("Updated Display Name");
+ assertEquals("Updated Display Name", realm.getDisplayName());
+ });
+ } finally {
+ adminClient.realm(realmName).remove();
+ }
+ }
}
diff --git a/tests/base/src/test/java/org/keycloak/tests/admin/realm/RealmSearchTest.java b/tests/base/src/test/java/org/keycloak/tests/admin/realm/RealmSearchTest.java
new file mode 100644
index 00000000000..d905b64df15
--- /dev/null
+++ b/tests/base/src/test/java/org/keycloak/tests/admin/realm/RealmSearchTest.java
@@ -0,0 +1,120 @@
+package org.keycloak.tests.admin.realm;
+
+import org.junit.jupiter.api.Test;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RealmProvider;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@KeycloakIntegrationTest
+public class RealmSearchTest extends AbstractRealmTest {
+
+ @Test
+ public void testSearchRealmByName() {
+ String realmName1 = "testSearchRealmA";
+ String realmName2 = "testSearchRealmB";
+ String realmName3 = "anotherRealmC";
+
+ // Create realms
+ RealmRepresentation rep1 = new RealmRepresentation();
+ rep1.setRealm(realmName1);
+ adminClient.realms().create(rep1);
+
+ RealmRepresentation rep2 = new RealmRepresentation();
+ rep2.setRealm(realmName2);
+ adminClient.realms().create(rep2);
+
+ RealmRepresentation rep3 = new RealmRepresentation();
+ rep3.setRealm(realmName3);
+ adminClient.realms().create(rep3);
+
+ try {
+ runOnServer.run(session -> {
+ RealmProvider realmProvider = session.realms();
+
+ List realmNames = realmProvider.getRealmsStream("testSearch")
+ .map(RealmModel::getName)
+ .collect(Collectors.toList());
+
+ assertTrue(realmNames.contains(realmName1), "Should find realm1");
+ assertTrue(realmNames.contains(realmName2), "Should find realm2");
+ assertFalse(realmNames.contains(realmName3), "Should not find realm3");
+
+ List realmNames2 = realmProvider.getRealmsStream("anotherRealmC")
+ .map(RealmModel::getName)
+ .collect(Collectors.toList());
+
+ assertTrue(realmNames2.contains(realmName3), "Should find realm3");
+ });
+ } finally {
+ adminClient.realm(realmName1).remove();
+ adminClient.realm(realmName2).remove();
+ adminClient.realm(realmName3).remove();
+ }
+ }
+
+ @Test
+ public void testSearchRealmByDisplayName() {
+ String realmName1 = "testSearchRealm1";
+ String realmName2 = "testSearchRealm2";
+ String realmName3 = "testSearchRealm3";
+
+ String displayName1 = "Unique Display Name ABC";
+ String displayName2 = "Different Name XYZ";
+ String displayName3 = "Another Unique Display";
+
+ RealmRepresentation rep1 = new RealmRepresentation();
+ rep1.setRealm(realmName1);
+ rep1.setDisplayName(displayName1);
+ adminClient.realms().create(rep1);
+
+ RealmRepresentation rep2 = new RealmRepresentation();
+ rep2.setRealm(realmName2);
+ rep2.setDisplayName(displayName2);
+ adminClient.realms().create(rep2);
+
+ RealmRepresentation rep3 = new RealmRepresentation();
+ rep3.setRealm(realmName3);
+ rep3.setDisplayName(displayName3);
+ adminClient.realms().create(rep3);
+
+ try {
+ runOnServer.run(session -> {
+ RealmProvider realmProvider = session.realms();
+
+ List realmNames = realmProvider.getRealmsStream("Unique")
+ .map(RealmModel::getName)
+ .collect(Collectors.toList());
+
+ assertTrue(realmNames.contains(realmName1));
+ assertFalse(realmNames.contains(realmName2));
+ assertTrue(realmNames.contains(realmName3));
+
+ List realmNames2 = realmProvider.getRealmsStream("XYZ")
+ .map(RealmModel::getName)
+ .collect(Collectors.toList());
+
+ assertFalse(realmNames2.contains(realmName1));
+ assertTrue(realmNames2.contains(realmName2));
+ assertFalse(realmNames2.contains(realmName3));
+
+ List realmNames3 = realmProvider.getRealmsStream(realmName1)
+ .map(RealmModel::getName)
+ .collect(Collectors.toList());
+
+ assertTrue(realmNames3.contains(realmName1));
+ });
+ } finally {
+ adminClient.realm(realmName1).remove();
+ adminClient.realm(realmName2).remove();
+ adminClient.realm(realmName3).remove();
+ }
+ }
+}
diff --git a/tests/base/src/test/java/org/keycloak/tests/admin/realm/RealmUpdateTest.java b/tests/base/src/test/java/org/keycloak/tests/admin/realm/RealmUpdateTest.java
index c0957f95d1c..0e0fc9ffe2e 100644
--- a/tests/base/src/test/java/org/keycloak/tests/admin/realm/RealmUpdateTest.java
+++ b/tests/base/src/test/java/org/keycloak/tests/admin/realm/RealmUpdateTest.java
@@ -257,11 +257,13 @@ public class RealmUpdateTest extends AbstractRealmTest {
assertThat(adminClient.realm(realmName).components().query(null, UserProfileProvider.class.getName()), empty());
- rep.setDisplayName("displayName");
+ String displayName = "displayName";
+ rep.setDisplayName(displayName);
adminClient.realm(realmName).update(rep);
// this used to return non-empty collection
assertThat(adminClient.realm(realmName).components().query(null, UserProfileProvider.class.getName()), empty());
+ assertEquals(displayName, adminClient.realm(realmName).toRepresentation().getDisplayName());
adminClient.realms().realm(realmName).remove();
}