From 1ccd502d1144b4f47bc2bb2d7e2b057886ad78dc Mon Sep 17 00:00:00 2001 From: Jeremy Parr-Pearson Date: Wed, 2 Sep 2026 23:01:18 +0000 Subject: [PATCH 1/5] Bump valkey-glide to 2.5.2 Signed-off-by: Jeremy Parr-Pearson --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f099cf44..ebc6e885 100644 --- a/pom.xml +++ b/pom.xml @@ -30,7 +30,7 @@ 4.1.0 - 2.4.2 + 2.5.2 7.5.2.RELEASE 7.4.1 2.12.0 From fe895afe924daa83b9fb8005236a529857c350a0 Mon Sep 17 00:00:00 2001 From: Jeremy Parr-Pearson Date: Wed, 2 Sep 2026 23:08:08 +0000 Subject: [PATCH 2/5] Fix cluster SAVE failing on GLIDE 2.5.2 with "No multi value stored" Signed-off-by: Jeremy Parr-Pearson --- .../valkeyglide/ClusterGlideClientAdapter.java | 7 +++++-- .../ValkeyGlideClusterServerCommands.java | 16 ++++++++++------ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/ClusterGlideClientAdapter.java b/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/ClusterGlideClientAdapter.java index 223be4d1..402695bc 100644 --- a/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/ClusterGlideClientAdapter.java +++ b/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/ClusterGlideClientAdapter.java @@ -415,8 +415,11 @@ public Object customCommand(GlideString[] args) throws InterruptedException, Exe ClusterValue clusterValue = nextCommandRoute == null ? glideClusterClient.customCommand(args).get() : glideClusterClient.customCommand(args, nextCommandRoute).get(); - // Case 1: Explicit multi-node route - return multiValue for aggregation - if (needToAggregateResult(nextCommandRoute)) { + // Case 1: Explicit multi-node route - return multiValue for aggregation. + // Guard with hasMultiData(): some commands routed to all primaries (e.g. SAVE) + // return a single value rather than a per-node map, in which case we fall + // through to single-value handling instead of failing. + if (needToAggregateResult(nextCommandRoute) && clusterValue.hasMultiData()) { return clusterValue.getMultiValue(); } diff --git a/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/ValkeyGlideClusterServerCommands.java b/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/ValkeyGlideClusterServerCommands.java index 53f9c1c7..c74b2b10 100644 --- a/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/ValkeyGlideClusterServerCommands.java +++ b/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/ValkeyGlideClusterServerCommands.java @@ -58,14 +58,17 @@ public ValkeyGlideClusterServerCommands(ValkeyGlideClusterConnection connection) @Override public void bgReWriteAof() { - // valkey-glide default route is bogus - by first key, so we specify ALL_PRIMARIES - connection.execute(SimpleMultiNodeRoute.ALL_PRIMARIES, "BGREWRITEAOF", (Map rawResult) -> null); + // valkey-glide default route is bogus - by first key, so we specify ALL_PRIMARIES. + // The response is discarded; accept Object since 2.5.x may return a single value + // rather than a per-node map for these admin commands. + connection.execute(SimpleMultiNodeRoute.ALL_PRIMARIES, "BGREWRITEAOF", (Object rawResult) -> null); } @Override public void bgSave() { - // valkey-glide default route is - random, so we specify ALL_PRIMARIES - connection.execute(SimpleMultiNodeRoute.ALL_PRIMARIES, "BGSAVE", (Map rawResult) -> null); + // valkey-glide default route is - random, so we specify ALL_PRIMARIES. + // The response is discarded; accept Object (single value or per-node map). + connection.execute(SimpleMultiNodeRoute.ALL_PRIMARIES, "BGSAVE", (Object rawResult) -> null); } @Override @@ -77,8 +80,9 @@ public Long lastSave() { } public void save() { - // valkey-glide default route is "random", so we specify ALL_PRIMARIES - connection.execute(SimpleMultiNodeRoute.ALL_PRIMARIES, "SAVE", (Map rawResult) -> null); + // valkey-glide default route is "random", so we specify ALL_PRIMARIES. + // The response is discarded; accept Object (single value or per-node map). + connection.execute(SimpleMultiNodeRoute.ALL_PRIMARIES, "SAVE", (Object rawResult) -> null); } @Override From d40067a668050c7df6a094cc1e85dbf370546d1a Mon Sep 17 00:00:00 2001 From: Jeremy Parr-Pearson Date: Wed, 2 Sep 2026 23:14:10 +0000 Subject: [PATCH 3/5] Adopt GLIDE clientInfoTag convention Signed-off-by: Jeremy Parr-Pearson --- .../connection/valkeyglide/ClusterGlideClientAdapter.java | 4 ++-- .../connection/valkeyglide/StandaloneGlideClientAdapter.java | 4 ++-- ...yGlideConnectionClusterServerCommandsIntegrationTests.java | 3 ++- .../ValkeyGlideConnectionServerCommandsIntegrationTests.java | 3 ++- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/ClusterGlideClientAdapter.java b/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/ClusterGlideClientAdapter.java index 402695bc..14ea0df6 100644 --- a/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/ClusterGlideClientAdapter.java +++ b/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/ClusterGlideClientAdapter.java @@ -175,8 +175,8 @@ else if (!password.equals(ValkeyPassword.none())) { subConfigBuilder.callback((msg, context) -> this.listener.onMessage(msg, context)); configBuilder.subscriptionConfiguration(subConfigBuilder.build()); - // Set library name for server-side client identification - configBuilder.libName("GlideSpringDataValkey"); + // Report as GlideJava(SpringDataValkey), preserving the underlying driver identity. + configBuilder.clientInfoTag("SpringDataValkey"); // Build and create cluster client GlideClusterClientConfiguration config = configBuilder.build(); diff --git a/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/StandaloneGlideClientAdapter.java b/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/StandaloneGlideClientAdapter.java index 37bb5928..9b3b1dd0 100644 --- a/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/StandaloneGlideClientAdapter.java +++ b/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/StandaloneGlideClientAdapter.java @@ -163,8 +163,8 @@ else if (!password.equals(ValkeyPassword.none())) { subConfigBuilder.callback((msg, context) -> this.listener.onMessage(msg, context)); configBuilder.subscriptionConfiguration(subConfigBuilder.build()); - // Set library name for server-side client identification - configBuilder.libName("GlideSpringDataValkey"); + // Report as GlideJava(SpringDataValkey), preserving the underlying driver identity. + configBuilder.clientInfoTag("SpringDataValkey"); // Build and create client GlideClientConfiguration config = configBuilder.build(); diff --git a/spring-data-valkey/src/test/java/io/valkey/springframework/data/valkey/connection/valkeyglide/ValkeyGlideConnectionClusterServerCommandsIntegrationTests.java b/spring-data-valkey/src/test/java/io/valkey/springframework/data/valkey/connection/valkeyglide/ValkeyGlideConnectionClusterServerCommandsIntegrationTests.java index 77a9e375..f805a608 100644 --- a/spring-data-valkey/src/test/java/io/valkey/springframework/data/valkey/connection/valkeyglide/ValkeyGlideConnectionClusterServerCommandsIntegrationTests.java +++ b/spring-data-valkey/src/test/java/io/valkey/springframework/data/valkey/connection/valkeyglide/ValkeyGlideConnectionClusterServerCommandsIntegrationTests.java @@ -478,7 +478,8 @@ void testGetClientListOnSpecificNode() { @EnabledOnValkeyVersion("7.2") void testClientLibNameReported() { List clientList = clusterConnection.serverCommands().getClientList(); - assertThat(clientList).anyMatch(client -> "GlideSpringDataValkey".equals(client.get("lib-name"))); + assertThat(clientList).anyMatch( + client -> client.get("lib-name") != null && client.get("lib-name").contains("(SpringDataValkey)")); } // ==================== Cluster-Wide Routing and Aggregation Verification diff --git a/spring-data-valkey/src/test/java/io/valkey/springframework/data/valkey/connection/valkeyglide/ValkeyGlideConnectionServerCommandsIntegrationTests.java b/spring-data-valkey/src/test/java/io/valkey/springframework/data/valkey/connection/valkeyglide/ValkeyGlideConnectionServerCommandsIntegrationTests.java index 26693718..6de5cb91 100644 --- a/spring-data-valkey/src/test/java/io/valkey/springframework/data/valkey/connection/valkeyglide/ValkeyGlideConnectionServerCommandsIntegrationTests.java +++ b/spring-data-valkey/src/test/java/io/valkey/springframework/data/valkey/connection/valkeyglide/ValkeyGlideConnectionServerCommandsIntegrationTests.java @@ -481,7 +481,8 @@ void testGetClientList() { @EnabledOnValkeyVersion("7.2") void testClientLibNameReported() { List clientList = connection.serverCommands().getClientList(); - assertThat(clientList).anyMatch(client -> "GlideSpringDataValkey".equals(client.get("lib-name"))); + assertThat(clientList).anyMatch( + client -> client.get("lib-name") != null && client.get("lib-name").contains("(SpringDataValkey)")); } @Test From 85e88cb704a977b955d8d182ff5482b2eb58f9f6 Mon Sep 17 00:00:00 2001 From: Jeremy Parr-Pearson Date: Thu, 3 Sep 2026 22:23:57 +0000 Subject: [PATCH 4/5] Scope SAVE fix and use version placeholder in docs Signed-off-by: Jeremy Parr-Pearson --- docs/src/content/docs/valkey/drivers.md | 6 +++--- .../ValkeyGlideClusterServerCommands.java | 14 ++++++-------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/docs/src/content/docs/valkey/drivers.md b/docs/src/content/docs/valkey/drivers.md index b5aa29ef..c4db4d2f 100644 --- a/docs/src/content/docs/valkey/drivers.md +++ b/docs/src/content/docs/valkey/drivers.md @@ -73,7 +73,7 @@ The following overview explains features that are supported by the individual Va io.valkey valkey-glide - 2.4.0 + ${version} ``` @@ -136,7 +136,7 @@ For more detailed client configuration options, see `io.valkey.springframework.d io.lettuce lettuce-core - 6.3.2.RELEASE + ${version} @@ -212,7 +212,7 @@ Netty currently supports the epoll (Linux) and kqueue (BSD/macOS) interfaces for valkey.clients jedis - 5.1.2 + ${version} diff --git a/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/ValkeyGlideClusterServerCommands.java b/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/ValkeyGlideClusterServerCommands.java index c74b2b10..1378212f 100644 --- a/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/ValkeyGlideClusterServerCommands.java +++ b/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/ValkeyGlideClusterServerCommands.java @@ -58,17 +58,14 @@ public ValkeyGlideClusterServerCommands(ValkeyGlideClusterConnection connection) @Override public void bgReWriteAof() { - // valkey-glide default route is bogus - by first key, so we specify ALL_PRIMARIES. - // The response is discarded; accept Object since 2.5.x may return a single value - // rather than a per-node map for these admin commands. - connection.execute(SimpleMultiNodeRoute.ALL_PRIMARIES, "BGREWRITEAOF", (Object rawResult) -> null); + // valkey-glide default route is bogus - by first key, so we specify ALL_PRIMARIES + connection.execute(SimpleMultiNodeRoute.ALL_PRIMARIES, "BGREWRITEAOF", (Map rawResult) -> null); } @Override public void bgSave() { - // valkey-glide default route is - random, so we specify ALL_PRIMARIES. - // The response is discarded; accept Object (single value or per-node map). - connection.execute(SimpleMultiNodeRoute.ALL_PRIMARIES, "BGSAVE", (Object rawResult) -> null); + // valkey-glide default route is - random, so we specify ALL_PRIMARIES + connection.execute(SimpleMultiNodeRoute.ALL_PRIMARIES, "BGSAVE", (Map rawResult) -> null); } @Override @@ -81,7 +78,8 @@ public Long lastSave() { public void save() { // valkey-glide default route is "random", so we specify ALL_PRIMARIES. - // The response is discarded; accept Object (single value or per-node map). + // GLIDE 2.5.x aggregates SAVE to a single value (rather than a per-node map); + // the response is discarded, so accept Object. connection.execute(SimpleMultiNodeRoute.ALL_PRIMARIES, "SAVE", (Object rawResult) -> null); } From 6e17cb1eced71fc81ef2d5a85cd01f140bb16de0 Mon Sep 17 00:00:00 2001 From: Jeremy Parr-Pearson Date: Thu, 3 Sep 2026 22:32:13 +0000 Subject: [PATCH 5/5] Extract CLIENT_INFO_TAG constant Signed-off-by: Jeremy Parr-Pearson --- .../connection/valkeyglide/ClusterGlideClientAdapter.java | 2 +- .../valkeyglide/StandaloneGlideClientAdapter.java | 2 +- .../valkey/connection/valkeyglide/UnifiedGlideClient.java | 6 ++++++ .../valkeyglide/ValkeyGlideClusterServerCommands.java | 1 + 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/ClusterGlideClientAdapter.java b/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/ClusterGlideClientAdapter.java index 14ea0df6..cbc24a2a 100644 --- a/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/ClusterGlideClientAdapter.java +++ b/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/ClusterGlideClientAdapter.java @@ -176,7 +176,7 @@ else if (!password.equals(ValkeyPassword.none())) { configBuilder.subscriptionConfiguration(subConfigBuilder.build()); // Report as GlideJava(SpringDataValkey), preserving the underlying driver identity. - configBuilder.clientInfoTag("SpringDataValkey"); + configBuilder.clientInfoTag(CLIENT_INFO_TAG); // Build and create cluster client GlideClusterClientConfiguration config = configBuilder.build(); diff --git a/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/StandaloneGlideClientAdapter.java b/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/StandaloneGlideClientAdapter.java index 9b3b1dd0..78a54e2c 100644 --- a/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/StandaloneGlideClientAdapter.java +++ b/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/StandaloneGlideClientAdapter.java @@ -164,7 +164,7 @@ else if (!password.equals(ValkeyPassword.none())) { configBuilder.subscriptionConfiguration(subConfigBuilder.build()); // Report as GlideJava(SpringDataValkey), preserving the underlying driver identity. - configBuilder.clientInfoTag("SpringDataValkey"); + configBuilder.clientInfoTag(CLIENT_INFO_TAG); // Build and create client GlideClientConfiguration config = configBuilder.build(); diff --git a/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/UnifiedGlideClient.java b/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/UnifiedGlideClient.java index 58e73f28..50661f18 100644 --- a/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/UnifiedGlideClient.java +++ b/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/UnifiedGlideClient.java @@ -29,6 +29,12 @@ */ interface UnifiedGlideClient extends AutoCloseable { + /** + * Client information tag reported to the server via GLIDE's {@code clientInfoTag}, producing a + * lib-name of {@code GlideJava(SpringDataValkey)} for server-side identification. + */ + String CLIENT_INFO_TAG = "SpringDataValkey"; + public enum BatchStatus { None, Pipeline, Transaction, diff --git a/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/ValkeyGlideClusterServerCommands.java b/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/ValkeyGlideClusterServerCommands.java index 1378212f..fa366138 100644 --- a/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/ValkeyGlideClusterServerCommands.java +++ b/spring-data-valkey/src/main/java/io/valkey/springframework/data/valkey/connection/valkeyglide/ValkeyGlideClusterServerCommands.java @@ -76,6 +76,7 @@ public Long lastSave() { (Map rawResult) -> rawResult.isEmpty() ? null : Collections.max(rawResult.values())); } + @Override public void save() { // valkey-glide default route is "random", so we specify ALL_PRIMARIES. // GLIDE 2.5.x aggregates SAVE to a single value (rather than a per-node map);