From 2d36ad26e42f3deed833b4f56ebf79ef4df20c7c Mon Sep 17 00:00:00 2001 From: Andy Stark Date: Fri, 10 Jul 2026 10:57:08 +0100 Subject: [PATCH 1/2] DOC-6829 Add preemptive Spring Data Redis JSON docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Document the new template-layer JSON API from the in-progress SDR PR spring-projects/spring-data-redis#3390 ahead of its release. The page lives under content/integrate/spring-framework-cache/, which despite the directory name is the Spring Data Redis section (LinkTitle "Spring Data Redis"), alongside the cache and geo-failover recipes. The non-obvious call: no per-client split. RedisJsonTemplate/JsonOperations is client-agnostic and identical whether Jedis or Lettuce backs the connection — the clients diverge only in internal converters/path types below the API surface, with full feature parity. So one set of examples serves both. Examples are inline java (not runnable TCE snippets) because the feature isn't in the client example repos yet, and a bannerText flags the API as unreleased and subject to change. Learned: SDR JSON template API is unified across Jedis/Lettuce; docs based on an unmerged PR Directive: keep the JSON examples client-agnostic — do not split into Jedis vs Lettuce variants Recheck: when spring-data-redis#3390 merges and ships — re-verify method signatures, bean/serializer wiring, and convert examples to runnable TCE doctests Gaps: code examples untested; signatures and API shape read from an in-progress, still-reviewed diff Ticket: DOC-6829 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../spring-framework-cache/_index.md | 1 + .../integrate/spring-framework-cache/json.md | 200 ++++++++++++++++++ 2 files changed, 201 insertions(+) create mode 100644 content/integrate/spring-framework-cache/json.md diff --git a/content/integrate/spring-framework-cache/_index.md b/content/integrate/spring-framework-cache/_index.md index f1dd2f4227..475f88054c 100644 --- a/content/integrate/spring-framework-cache/_index.md +++ b/content/integrate/spring-framework-cache/_index.md @@ -26,3 +26,4 @@ The pages in this section describe recipes for using Redis from Spring Data Redi - [Use Redis with the Spring cache abstraction]({{< relref "/integrate/spring-framework-cache/cache" >}}) shows how to use Redis as the storage for Spring's cache abstraction. - [Client-side geographic failover]({{< relref "/integrate/spring-framework-cache/geo-failover" >}}) shows how to configure resilient connections that automatically fail over between Redis endpoints. +- [Use JSON documents with Spring Data Redis]({{< relref "/integrate/spring-framework-cache/json" >}}) shows how to store, retrieve, and update JSON documents with the template-based JSON API. diff --git a/content/integrate/spring-framework-cache/json.md b/content/integrate/spring-framework-cache/json.md new file mode 100644 index 0000000000..846140b235 --- /dev/null +++ b/content/integrate/spring-framework-cache/json.md @@ -0,0 +1,200 @@ +--- +LinkTitle: JSON support +Title: Use JSON documents with Spring Data Redis +alwaysopen: false +categories: +- docs +- integrate +- stack +- oss +- rs +- rc +- oss +- client +description: Store, retrieve, and update JSON documents from a Spring Data Redis + application. +group: framework +summary: Spring Data Redis provides a template-based, fluent API for working with + Redis JSON documents, including path-based updates and type-specific operations. +type: integration +weight: 30 +bannerText: JSON support in Spring Data Redis is not yet released and the API is subject to change. This page is based on the in-progress pull request [spring-data-redis#3390](https://github.com/spring-projects/spring-data-redis/pull/3390) and will be updated when the feature ships. +relatedPages: +- /develop/data-types/json +- /develop/data-types/json/path +- /develop/clients/lettuce +- /develop/clients/jedis +--- + +Spring Data Redis lets you work with +[JSON]({{< relref "/develop/data-types/json" >}}) documents through a +template-based, fluent API. This builds on the underlying +[Lettuce]({{< relref "/develop/clients/lettuce" >}}) and +[Jedis]({{< relref "/develop/clients/jedis" >}}) clients, so your Spring +application can store, retrieve, and update JSON documents without dropping +down to the low-level command API. + +The JSON API serializes your own Java objects to and from JSON using a +dedicated serializer, and supports [JSON path]({{< relref "/develop/data-types/json/path" >}}) +expressions so you can read and update parts of a document without +transferring the whole thing. + +## Requirements + +To use the JSON support, you need: + +- A Redis server with the JSON capability, such as + [Redis Open Source]({{< relref "/operate/oss_and_stack/" >}}) 8 or later, + or [Redis Stack]({{< relref "/operate/oss_and_stack/install/install-stack/" >}}). +- The Lettuce or Jedis client on your classpath (Spring Data Redis works with + either). +- A JSON library. The examples below use + [Jackson](https://github.com/FasterXML/jackson), which Spring Data Redis + uses by default for JSON serialization. + +## Set up + +Add Spring Data Redis to your build. For Maven, edit your `pom.xml`: + +```xml + + org.springframework.data + spring-data-redis + + {version} + +``` + +For Gradle, add the following to your `build.gradle`: + +```groovy +implementation 'org.springframework.data:spring-data-redis:{version}' +``` + +Configure a `RedisJsonTemplate` bean, backed by your existing connection +factory and a `JacksonRedisJsonSerializer` for the document values. Use +`StringRedisJsonTemplate` if you want `String` keys and values without any +extra configuration: + +```java +@Configuration +public class RedisJsonConfig { + + @Bean + public RedisJsonTemplate redisJsonTemplate( + RedisConnectionFactory connectionFactory) { + + RedisJsonTemplate template = new RedisJsonTemplate<>(); + template.setConnectionFactory(connectionFactory); + template.setKeySerializer(RedisSerializer.string()); + template.setJsonSerializer(new JacksonRedisJsonSerializer<>(User.class)); + template.afterPropertiesSet(); + return template; + } +} +``` + +The examples below use a simple `User` type: + +```java +public class User { + private String name; + private int age; + private String city; + private List interests; + + // Constructors, getters, and setters omitted. +} +``` + +## Store and retrieve a document + +Use the `value()` spec to work with a whole document. Call `set()` to store an +object as a JSON document, and `get()` to read it back: + +```java +User user = new User("Paul", 42, "London", List.of("golf", "coding")); + +// Store the object as a JSON document at the key "user:1". +jsonTemplate.value("user:1").set(user); + +// Read the whole document back as a User object. +JsonResult result = jsonTemplate.value("user:1").get(); +User stored = result.getValue(); +``` + +## Work with JSON paths + +Pass a [JSON path]({{< relref "/develop/data-types/json/path" >}}) to `path()` +to target part of a document instead of the whole thing. This lets you read or +update a single field without rewriting the entire object. + +```java +// Update just the "city" field. +jsonTemplate.value("user:1").path("$.city").set("Manchester"); + +// Set a field only if it does not already exist. +jsonTemplate.value("user:1").path("$.nickname").setIfAbsent("Paulie"); + +// Read a specific path. +JsonResult city = jsonTemplate.value("user:1").paths("$.city"); +``` + +## Update arrays + +Use the `array()` spec for array fields. You can append elements, read the +current length, trim the array to a range, or find the index of a value: + +```java +// Append a value to the "interests" array. +jsonTemplate.array("user:1").path("$.interests").append("cycling"); + +// Get the length of the array. +List lengths = jsonTemplate.array("user:1").path("$.interests").length(); + +// Find the index of a value. +List index = jsonTemplate.array("user:1").path("$.interests").indexOf("golf"); + +// Keep only the first two elements. +jsonTemplate.array("user:1").path("$.interests").trim(0, 1); +``` + +## Update strings + +Use the `string()` spec to append to string fields and read their length: + +```java +// Append to a string field. +jsonTemplate.string("user:1").path("$.name").append(" Jones"); + +// Get the length of the string. +List nameLength = jsonTemplate.string("user:1").path("$.name").length(); +``` + +## Toggle booleans + +Use the `bool()` spec to flip boolean fields between `true` and `false`: + +```java +// Flip the "active" flag. +List newValues = jsonTemplate.bool("user:1").path("$.active").toggle(); +``` + +## Merge into a document + +Use `mergeWith()` to merge new data into an existing document, following the +[JSON merge]({{< relref "/commands/json.merge" >}}) semantics. Fields in the +supplied object are added or overwritten, and setting a field to `null` +removes it: + +```java +// Merge in a partial object to update several fields at once. +Map changes = Map.of("city", "Leeds", "age", 43); +jsonTemplate.value("user:1").mergeWith(changes); +``` + +## Further reading + +- [Redis JSON data type]({{< relref "/develop/data-types/json" >}}) +- [JSON path syntax]({{< relref "/develop/data-types/json/path" >}}) +- [Spring Data Redis reference documentation](https://docs.spring.io/spring-data/redis/reference/) From 363dd62427bddfc8848868043da070d72b578edc Mon Sep 17 00:00:00 2001 From: Andy Stark Date: Wed, 26 Aug 2026 10:29:24 +0100 Subject: [PATCH 2/2] DOC-6829 Reconcile the SDR JSON page against the released 4.2.0-M1 API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The API this page was written against shipped in spring-data-redis 4.2.0-M1 (2026-08-21). Reconciling the page against the released source fixed six defects, four of them compile errors. Still parked: 4.2.0-M1 is a milestone, not a GA release, so the API can still move before 4.2.0. Fixed: - Bean wiring. `RedisJsonTemplate` has ONE type parameter (the key type) and is constructor-injected, not a no-arg bean with setters. The page's `RedisJsonTemplate` + `setJsonSerializer()` form does not exist in any released version. - `JacksonRedisJsonSerializer` and `StringRedisJsonTemplate` do not exist. - `JsonResult` is not generic, and `getValue()` is not one of its methods. Values come out via `as(Class)` / `asString()` / `map()`. - `value(key).paths(...)` exists on nothing. `paths` is a top-level `JsonOperations` method; the specs only carry `path(String)`. - Added `setIfPresent`, which exists as a default alongside `setIfAbsent`. - Banner rewritten: it still described #3390 as an in-progress PR. The most useful thing this taught: the park manifest's own correction note was itself wrong, and following it would have produced a second compile error. It offered `JacksonJsonRedisSerializer` and `GenericJacksonJsonRedisSerializer` as interchangeable replacements. They are not. `JacksonJsonRedisSerializer implements RedisSerializer`, while the constructor demands `RedisJsonSerializer` — only the two `Generic*` classes implement it. Identical-looking names, incompatible types, and the byte-identical blob check that discharged the "signatures" item could never have caught it, because these classes live in files the manifest never pinned. A discharge is only as wide as its pins. Second lesson: the manifest declared the fluent surface "correct as written" after a signature diff. Two of the six defects were in that surface. A signature diff proves nothing moved; it does not prove the page ever matched. Learned: a park manifest's correction notes need the same scepticism as the page they correct; a blob-identity discharge only covers the files it pins Ticket: DOC-6829 Constraint: RedisJsonTemplate's 3rd ctor arg must implement RedisJsonSerializer — only GenericJacksonJsonRedisSerializer (Jackson 3) and GenericJackson2JsonRedisSerializer (Jackson 2) do. Rejected: JacksonJsonRedisSerializer as the document serializer | implements RedisSerializer, not RedisJsonSerializer; will not compile Recheck: when spring-data-redis 4.2.0 reaches GA — re-diff both blobs at the GA tag and replace the {version} placeholder Gaps: no snippet was compiled against the 4.2.0-M1 artifact; every signature was read from source at the tag, which is weaker than a build Directive: BoundJsonOperations is absent at 4.2.0-M1 (0 hits, core/ listing confirms) — do not re-derive that check --- .../integrate/spring-framework-cache/json.md | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/content/integrate/spring-framework-cache/json.md b/content/integrate/spring-framework-cache/json.md index 846140b235..6b794045e2 100644 --- a/content/integrate/spring-framework-cache/json.md +++ b/content/integrate/spring-framework-cache/json.md @@ -18,7 +18,7 @@ summary: Spring Data Redis provides a template-based, fluent API for working wit Redis JSON documents, including path-based updates and type-specific operations. type: integration weight: 30 -bannerText: JSON support in Spring Data Redis is not yet released and the API is subject to change. This page is based on the in-progress pull request [spring-data-redis#3390](https://github.com/spring-projects/spring-data-redis/pull/3390) and will be updated when the feature ships. +bannerText: JSON support in Spring Data Redis is currently available only in the `4.2.0-M1` milestone release, and the API may still change before `4.2.0` becomes generally available. This page will be updated when `4.2.0` ships. relatedPages: - /develop/data-types/json - /develop/data-types/json/path @@ -71,29 +71,30 @@ For Gradle, add the following to your `build.gradle`: implementation 'org.springframework.data:spring-data-redis:{version}' ``` -Configure a `RedisJsonTemplate` bean, backed by your existing connection -factory and a `JacksonRedisJsonSerializer` for the document values. Use -`StringRedisJsonTemplate` if you want `String` keys and values without any -extra configuration: +Configure a `RedisJsonTemplate` bean. The template takes its connection +factory, a key serializer, and a JSON serializer as constructor arguments. +Its single type parameter is the *key* type, not the document type: ```java @Configuration public class RedisJsonConfig { @Bean - public RedisJsonTemplate redisJsonTemplate( + public RedisJsonTemplate redisJsonTemplate( RedisConnectionFactory connectionFactory) { - RedisJsonTemplate template = new RedisJsonTemplate<>(); - template.setConnectionFactory(connectionFactory); - template.setKeySerializer(RedisSerializer.string()); - template.setJsonSerializer(new JacksonRedisJsonSerializer<>(User.class)); - template.afterPropertiesSet(); - return template; + return new RedisJsonTemplate<>(connectionFactory, + RedisSerializer.string(), + GenericJacksonJsonRedisSerializer.builder().build()); } } ``` +The JSON serializer is not bound to a document type. Pass +`GenericJacksonJsonRedisSerializer` for Jackson 3 or +`GenericJackson2JsonRedisSerializer` for Jackson 2; you choose the Java type +when you read a value back. + The examples below use a simple `User` type: ```java @@ -119,8 +120,8 @@ User user = new User("Paul", 42, "London", List.of("golf", "coding")); jsonTemplate.value("user:1").set(user); // Read the whole document back as a User object. -JsonResult result = jsonTemplate.value("user:1").get(); -User stored = result.getValue(); +JsonResult result = jsonTemplate.value("user:1").get(); +User stored = result.as(User.class); ``` ## Work with JSON paths @@ -136,8 +137,12 @@ jsonTemplate.value("user:1").path("$.city").set("Manchester"); // Set a field only if it does not already exist. jsonTemplate.value("user:1").path("$.nickname").setIfAbsent("Paulie"); -// Read a specific path. -JsonResult city = jsonTemplate.value("user:1").paths("$.city"); +// Set a field only if it already exists. +jsonTemplate.value("user:1").path("$.city").setIfPresent("Bristol"); + +// Read a specific path. paths() is called on the template, not on a spec. +JsonResult city = jsonTemplate.paths("user:1", "$.city"); +String cityName = city.asString(); ``` ## Update arrays