This version is still in development and is not considered stable yet. For the latest stable version, please use Spring Data Redis 4.1.1!

Working with Objects through RedisTemplate

Most users are likely to use RedisTemplate and its corresponding package, org.springframework.data.redis.core or its reactive variant ReactiveRedisTemplate. The template is, in fact, the central class of the Redis module, due to its rich feature set. The template offers a high-level abstraction for Redis interactions. While [Reactive]RedisConnection offers low-level methods that accept and return binary values (byte arrays), the template takes care of serialization and connection management, freeing the user from dealing with such details.

The RedisTemplate class implements the RedisOperations interface and its reactive variant ReactiveRedisTemplate implements ReactiveRedisOperations.

To use Redis JSON, use RedisJsonTemplate. You can use Redis JSON (being an additional module) through a separate entry point featuring a fluent API that is different from the existing RedisTemplate approach, therefore, make sure to familiarize yourself with its specifics described in Working with Redis JSON.

The preferred way to reference operations on a [Reactive]RedisTemplate instance is through the [Reactive]RedisOperations interface.

Moreover, the template provides operations views (following the grouping from the Redis command reference) that offer rich, generified interfaces for working against a certain type or certain key (through the KeyBound interfaces) as described in the following table:

Operational views
  • Imperative

  • Reactive

Interface Description

Key Type Operations

GeoOperations

Redis geospatial operations, such as GEOADD, GEORADIUS,…​

HashOperations

Redis hash operations

HyperLogLogOperations

Redis HyperLogLog operations, such as PFADD, PFCOUNT,…​

ListOperations

Redis list operations

SetOperations

Redis set operations

ValueOperations

Redis string (or value) operations

ZSetOperations

Redis zset (or sorted set) operations

RedisJsonOperations

Redis JSON operations

Key Bound Operations

BoundGeoOperations

Redis key bound geospatial operations

BoundHashOperations

Redis hash key bound operations

BoundKeyOperations

Redis key bound operations

BoundListOperations

Redis list key bound operations

BoundSetOperations

Redis set key bound operations

BoundValueOperations

Redis string (or value) key bound operations

BoundZSetOperations

Redis zset (or sorted set) key bound operations

Interface Description

Key Type Operations

ReactiveGeoOperations

Redis geospatial operations such as GEOADD, GEORADIUS, and others)

ReactiveHashOperations

Redis hash operations

ReactiveHyperLogLogOperations

Redis HyperLogLog operations such as (PFADD, PFCOUNT, and others)

ReactiveListOperations

Redis list operations

ReactiveSetOperations

Redis set operations

ReactiveValueOperations

Redis string (or value) operations

ReactiveZSetOperations

Redis zset (or sorted set) operations

Once configured, the template is thread-safe and can be reused across multiple instances.

RedisTemplate uses a Java-based serializer for most of its operations. This means that any object written or read by the template is serialized and deserialized through Java.

You can change the serialization mechanism on the template, and the Redis module offers several implementations, which are available in the org.springframework.data.redis.serializer package. See Serializers for more information. You can also set any of the serializers to null and use RedisTemplate with raw byte arrays by setting the enableDefaultSerializer property to false. Note that the template requires all keys to be non-null. However, values can be null as long as the underlying serializer accepts them. Read the Javadoc of each serializer for more information.

For cases where you need a certain template view, declare the view as a dependency and inject the template. The container automatically performs the conversion, eliminating the opsFor[X] calls, as shown in the following example:

Configuring Template API
  • Java Imperative

  • Java Reactive

  • XML

@Configuration
class MyConfig {

  @Bean
  LettuceConnectionFactory connectionFactory() {
    return new LettuceConnectionFactory();
  }

  @Bean
  RedisTemplate<String, String> redisTemplate(RedisConnectionFactory connectionFactory) {

    RedisTemplate<String, String> template = new RedisTemplate<>();
    template.setConnectionFactory(connectionFactory);
    return template;
  }
}
@Configuration
class MyConfig {

  @Bean
  LettuceConnectionFactory connectionFactory() {
    return new LettuceConnectionFactory();
  }

  @Bean
  ReactiveRedisTemplate<String, String> ReactiveRedisTemplate(ReactiveRedisConnectionFactory connectionFactory) {
    return new ReactiveRedisTemplate<>(connectionFactory, RedisSerializationContext.string());
  }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory"/>
  <!-- redis template definition -->
  <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" p:connection-factory-ref="redisConnectionFactory"/>
  ...

</beans>
Pushing an item to a List using [Reactive]RedisTemplate
  • Imperative

  • Reactive

public class Example {

  // inject the actual operations
  @Autowired
  private RedisOperations<String, String> operations;

  // inject the template as ListOperations
  @Resource(name="redisTemplate")
  private ListOperations<String, String> listOps;

  public void addLink(String userId, URL url) {
    listOps.leftPush(userId, url.toExternalForm());
  }
}
public class Example {

  // inject the actual template
  @Autowired
  private ReactiveRedisOperations<String, String> operations;

  public Mono<Long> addLink(String userId, URL url) {
    return operations.opsForList().leftPush(userId, url.toExternalForm());
  }
}

Working with Redis JSON

RedisJsonTemplate provides a fluent API for storing JSON documents and accessing their values through Redis JSON commands. It converts between JSON and Java objects and lets you select values within a document through JSONPath expressions.

Storing and Reading Documents

You can create a RedisJsonTemplate from a RedisConnectionFactory. The following example stores a person with two addresses and reads the document back as a Java object:

record Address(String city) {}
record Person(String firstName, String lastName, List<Address> addresses) {}

RedisJsonTemplate<String> template = RedisJsonTemplate.create(connectionFactory);

template.set("user", new Person("John", "Doe",
    List.of(new Address("London"), new Address("Paris"))));

Person person = template.get("user").as(Person.class);

The get method returns a JsonOperations.JsonResult. Use its as method to convert the result to the required Java type.

Reading Values by Path

To read a value within a document, use value(key) with a JSONPath expression:

String firstName = template.value("user").path("$.firstName").get()
    .as(String.class); // "John"

Redis returns JSONPath matches in an array, including when the path selects a single value or the entire document ($). The as method unwraps this match array and converts the selected value, which can itself be an object, an array, or a scalar. It returns null if there are no matches and throws a SerializationException if there is more than one match.

For paths that can select several values, use matches() to obtain a JsonOperations.JsonResults. Its as method converts each match and returns a list. The following example reads the city from each address:

List<String> cities = template.value("user").path("$.addresses[*].city").get()
    .matches().as(String.class); // ["London", "Paris"]

You can use matches() for any number of matches, including zero. Wildcards, recursive descent, slices, unions, and filters can select more than one value.

Calling asString() on the result returns the entire match array as a single JSON string:

String json = template.value("user").path("$.addresses[*].city").get()
    .asString(); // ["London","Paris"]

Unlike as(String.class), asString() does not unwrap or convert the matched values and can return multiple matches. Use matches().as(String.class) for a Java List<String> or asString() for the JSON representation of the match array.

Reading Several Paths

The paths method reads several paths from one document in a single JSON.GET call. It returns a JsonOperations.JsonPathResult, which you can convert to an object containing the selected properties:

record Name(String firstName, String lastName) {}

Name name = template.paths("user", "firstName", "lastName").as(Name.class);

For property paths, such as firstName or lastName, the as method maps an object keyed by the requested property paths. In the preceding example, this object is {"firstName":"John","lastName":"Doe"}. Each path’s match array is unwrapped before conversion. A path with no matches contributes a JSON null value while a path with more than one match causes a SerializationException.

You can also use JSONPath expressions, in which case the object is keyed by those expressions. To read the results individually, use path(String) with the same path you requested. This also lets you read paths with multiple matches:

JsonPathResult result = template.paths("user", "$.firstName", "$.addresses[*].city");

String firstName = result.path("$.firstName").as(String.class);
List<String> cities = result.path("$.addresses[*].city").matches().as(String.class);

The template requires at least one path and rejects duplicate paths or a mixture of property paths and JSONPath expressions with an IllegalArgumentException. It also throws an IllegalArgumentException if Redis omits a requested path from the reply, as can happen with unsupported JSONPath expressions.

Absent Values and JSON null

JsonResult.as(…) returns null when the key does not exist, the path has no matches, or the selected value is JSON null. Use exists(), matches(), and isNull() to distinguish these cases:

Case exists() matches() isNull() as(…)

Missing key

false

empty

false

null

Path matched nothing

true

empty

false

null

Path matched JSON null

true

1 element

true

null

One non-null match

true

1 element

false

the converted value

Accessing Raw JSON

Use JsonResult.asBytes() or JsonResult.asString() to access the JSON reply without converting it to a Java object or unwrapping the match array. Both methods return null if the key does not exist. For example, reading $.firstName with asString() returns ["John"], including the match array.

You can access individual matches as JSON through matches().asBytes() or matches().asString(). See the Javadoc for details on preserving the original JSON representation with custom serializers.

JsonPathResult also provides asBytes() and asString() methods. These return the raw JSON.GET reply, including the match arrays and the JSONPath keys sent to Redis, rather than the object used for conversion through as(…). For a single requested path, the reply contains only the match array.

String-focused Convenience Classes

Since it is quite common for the keys and values stored in Redis to be java.lang.String, the Redis modules provides two extensions to RedisConnection and RedisTemplate, respectively the StringRedisConnection (and its DefaultStringRedisConnection implementation) and StringRedisTemplate as a convenient one-stop solution for intensive String operations. In addition to being bound to String keys, the template and the connection use the StringRedisSerializer underneath, which means the stored keys and values are human-readable (assuming the same encoding is used both in Redis and your code). The following listings show an example:

  • Java Imperative

  • Java Reactive

  • XML

@Configuration
class RedisConfiguration {

  @Bean
  LettuceConnectionFactory redisConnectionFactory() {
    return new LettuceConnectionFactory();
  }

  @Bean
  StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {

    StringRedisTemplate template = new StringRedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
  }
}
@Configuration
class RedisConfiguration {

  @Bean
  LettuceConnectionFactory redisConnectionFactory() {
    return new LettuceConnectionFactory();
  }

  @Bean
  ReactiveStringRedisTemplate reactiveRedisTemplate(ReactiveRedisConnectionFactory factory) {
    return new ReactiveStringRedisTemplate<>(factory);
  }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory"/>

  <bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate" p:connection-factory-ref="redisConnectionFactory"/>

</beans>
  • Imperative

  • Reactive

public class Example {

  @Autowired
  private StringRedisTemplate redisTemplate;

  public void addLink(String userId, URL url) {
    redisTemplate.opsForList().leftPush(userId, url.toExternalForm());
  }
}
public class Example {

  @Autowired
  private ReactiveStringRedisTemplate redisTemplate;

  public Mono<Long> addLink(String userId, URL url) {
    return redisTemplate.opsForList().leftPush(userId, url.toExternalForm());
  }
}

As with the other Spring templates, RedisTemplate and StringRedisTemplate let you talk directly to Redis through the RedisCallback interface. This feature gives complete control to you, as it talks directly to the RedisConnection. Note that the callback receives an instance of StringRedisConnection when a StringRedisTemplate is used. The following example shows how to use the RedisCallback interface:

public void useCallback() {

  redisOperations.execute(new RedisCallback<Object>() {
    public Object doInRedis(RedisConnection connection) throws DataAccessException {
      Long size = connection.dbSize();
      // Can cast to StringRedisConnection if using a StringRedisTemplate
      ((StringRedisConnection)connection).set("key", "value");
    }
   });
}

Serializers

From the framework perspective, the data stored in Redis is only bytes. While Redis itself supports various types, for the most part, these refer to the way the data is stored rather than what it represents. It is up to the user to decide whether the information gets translated into strings or any other objects.

In Spring Data, the conversion between the user (custom) types and raw data (and vice-versa) is handled by Spring Data Redis in the org.springframework.data.redis.serializer package.

This package contains two types of serializers that, as the name implies, take care of the serialization process:

  • Two-way serializers based on RedisSerializer.

  • Element readers and writers that use RedisElementReader and RedisElementWriter.

The main difference between these variants is that RedisSerializer primarily serializes to byte[] while readers and writers use ByteBuffer.

Multiple implementations are available (including two that have been already mentioned in this documentation):

However, one can use OxmSerializer for Object/XML mapping through Spring OXM support or JacksonJsonRedisSerializer or GenericJacksonJsonRedisSerializer for storing data in JSON format.

Do note that the storage format is not limited only to values. It can be used for keys, values, or hashes without any restrictions.

By default, RedisCache and RedisTemplate are configured to use Java native serialization. Java native serialization is known for allowing the running of remote code caused by payloads that exploit vulnerable libraries and classes injecting unverified bytecode. Manipulated input could lead to unwanted code being run in the application during the deserialization step. As a consequence, do not use serialization in untrusted environments. In general, we strongly recommend any other message format (such as JSON) instead.

If you are concerned about security vulnerabilities due to Java serialization, consider the general-purpose serialization filter mechanism at the core JVM level: