Micronaut Redis

Integration between Micronaut and Redis

Version: 7.0.0-SNAPSHOT

1 Introduction

Micronaut features automatic configuration of the Lettuce driver for Redis via the redis-lettuce compatibility module and the more focused redis-lettuce-core, redis-lettuce-cache, and redis-lettuce-session modules.

2 Release History

For this project, you can find a list of releases (with release notes) here:

3 Setting up the Redis Lettuce Driver

Using the CLI

If you are creating your project using the Micronaut CLI, supply the redis-lettuce feature to configure the Lettuce driver in your project:

$ mn create-app my-app --features redis-lettuce

To configure the Lettuce driver you should first add the redis-lettuce compatibility module to your classpath:

implementation("io.micronaut.redis:micronaut-redis-lettuce")
<dependency>
    <groupId>io.micronaut.redis</groupId>
    <artifactId>micronaut-redis-lettuce</artifactId>
</dependency>

If you only need part of the integration, dedicated modules are also available:

implementation("io.micronaut.redis:micronaut-redis-lettuce-core")
<dependency>
    <groupId>io.micronaut.redis</groupId>
    <artifactId>micronaut-redis-lettuce-core</artifactId>
</dependency>

implementation("io.micronaut.redis:micronaut-redis-lettuce-cache")
<dependency>
    <groupId>io.micronaut.redis</groupId>
    <artifactId>micronaut-redis-lettuce-cache</artifactId>
</dependency>

implementation("io.micronaut.redis:micronaut-redis-lettuce-session")
<dependency>
    <groupId>io.micronaut.redis</groupId>
    <artifactId>micronaut-redis-lettuce-session</artifactId>
</dependency>

If you want Micronaut Pub/Sub listeners, publishers, and @RedisPubSubClient, add the dedicated Pub/Sub module as well:

implementation("io.micronaut.redis:micronaut-redis-lettuce-pubsub")
<dependency>
    <groupId>io.micronaut.redis</groupId>
    <artifactId>micronaut-redis-lettuce-pubsub</artifactId>
</dependency>

You should then configure the URI of the Redis server you wish to communicate with in the application configuration file:

Configuring redis.uri
redis.uri=redis://localhost
redis:
    uri: redis://localhost
[redis]
  uri="redis://localhost"
redis {
  uri = "redis://localhost"
}
{
  redis {
    uri = "redis://localhost"
  }
}
{
  "redis": {
    "uri": "redis://localhost"
  }
}
The redis.uri setting should be in the format as described in the Connection URIs section of the Lettuce wiki
If Redis requires authentication, you can either include credentials directly in redis.uri, for example redis://:secret@localhost or redis://username:secret@localhost, or provide a password separately with either redis.authentication or redis.password alongside redis.uri.

You can also specify multiple Redis URIs using redis.uris in which case a RedisClusterClient is created instead.

Configuring Lettuce ClientResources and threads

You can provide a custom instance of io.lettuce.core.resource.ClientResources it will be used to create io.lettuce.core.RedisClient.

It’s possible to configure thread pool size without providing custom io.lettuce.core.resource.ClientResources:

redis:
    uri: redis://localhost
    io-thread-pool-size: 5
    computation-thread-pool-size: 4

Lettuce description of pool size properties

Name Default

I/O Thread Pool Size

Number of processors

The number of threads in the I/O thread pools. The number defaults to the number of available processors that the runtime returns (which, as a well-known fact, sometimes does not represent the actual number of processors). Every thread represents an internal event loop where all I/O tasks are run. The number does not reflect the actual number of I/O threads because the client requires different thread pools for Network (NIO) and Unix Domain Socket (EPoll) connections. The minimum I/O threads are 3. A pool with fewer threads can cause undefined behavior.

Computation Thread Pool Size

Number of processors

The number of threads in the computation thread pool. The number defaults to the number of available processors that the runtime returns (which, as a well-known fact, sometimes does not represent the actual number of processors). Every thread represents an internal event loop where all computation tasks are run. The minimum computation threads are 3. A pool with fewer threads can cause undefined behavior.

You may see io.lettuce.core.RedisCommandTimeoutException: Command timed out after if your code is blocking Lettuce’s asynchronous execution because of the default value of the thread pool size being small.
Increasing io-thread-pool-size or computation-thread-pool-size does not make a single StatefulRedisConnection process commands on multiple Redis connections. A long-lived Lettuce connection still uses a single underlying channel. If you need parallel reactive work across multiple Redis connections, enable redis.pool and borrow separate connections from the pool.

Pooled connections for parallel reactive commands

Micronaut can create a Lettuce connection pool for application code:

redis.uri=redis://localhost
redis.pool.enabled=true
redis.pool.max-total=16
redis.pool.min-idle=4
redis:
    uri: redis://localhost
    pool:
        enabled: true
        max-total: 16
        min-idle: 4
[redis]
  uri="redis://localhost"
  [redis.pool]
    enabled=true
    max-total=16
    min-idle=4
redis {
  uri = "redis://localhost"
  pool {
    enabled = true
    maxTotal = 16
    minIdle = 4
  }
}
{
  redis {
    uri = "redis://localhost"
    pool {
      enabled = true
      max-total = 16
      min-idle = 4
    }
  }
}
{
  "redis": {
    "uri": "redis://localhost",
    "pool": {
      "enabled": true,
      "max-total": 16,
      "min-idle": 4
    }
  }
}

Then inject io.lettuce.core.support.AsyncPool<io.lettuce.core.api.StatefulRedisConnection<K, V>> and release each acquired connection after use:

first.sync().set("first", "one")
second.sync().set("second", "two")

If you configure Redis as a cluster with redis.uris, Micronaut instead exposes io.lettuce.core.support.AsyncPool<io.lettuce.core.cluster.api.StatefulRedisClusterConnection<K, V>> so you can borrow pooled cluster connections for the same pattern.

Available Lettuce Beans

Once you have the above configuration in place you can inject one of the following beans:

  • io.lettuce.core.RedisClient - The main client interface

  • io.lettuce.core.api.StatefulRedisConnection - A connection interface that features synchronous, reactive (based on Reactor) and async APIs that operate on String values

  • io.lettuce.core.support.AsyncPool<io.lettuce.core.api.StatefulRedisConnection<K, V>> - An optional pool for borrowing multiple connections when you need parallel Redis work

  • io.lettuce.core.support.AsyncPool<io.lettuce.core.cluster.api.StatefulRedisClusterConnection<K, V>> - An optional cluster-aware pool available when redis.uris is configured

  • io.lettuce.core.pubsub.StatefulRedisPubSubConnection - A connection interface for dealing with Redis Pub/Sub

The following example demonstrates the use of the StatefulRedisConnection interface’s synchronous API:

Using StatefulRedisConnection
    @Inject StatefulRedisConnection<String, String> connection;

    void commands() {
        RedisCommands<String, String> commands = connection.sync();
        commands.set("foo", "bar");
        commands.get("foo");
    }
Using StatefulRedisConnection
    @Inject
    lateinit var connection: StatefulRedisConnection<String, String>

    fun commands() {
        val commands: RedisCommands<String, String> = connection.sync()
        commands.set("foo", "bar")
        commands.get("foo")
    }
Using StatefulRedisConnection
    @Inject StatefulRedisConnection<String, String> connection

    void commands() {
        RedisCommands<String, String> commands = connection.sync()
        commands.set("foo", "bar")
        commands.get("foo")
    }
The Lettuce driver’s StatefulRedisConnection interface is designed to be long-lived and there is no need to close the connection. It will be closed automatically when the application shuts down.

Redis codec configuration

By default, a StringCodec is used for redis connections. This can be configured by supplying your own RedisCodec bean.

Configuring a custom RedisCodec
@Factory
public class ByteArrayCodecFactory {

    @Singleton
    RedisCodec<byte[], byte[]> redisCodec() {
        return ByteArrayCodec.INSTANCE;
    }
}
Configuring a custom RedisCodec
@Factory
class ByteArrayCodecFactory {

    @Singleton
    fun redisCodec(): RedisCodec<ByteArray, ByteArray> = ByteArrayCodec.INSTANCE
}
Configuring a custom RedisCodec
@CompileStatic
@Factory
class ByteArrayCodecFactory {

    @Singleton
    RedisCodec<byte[], byte[]> redisCodec() {
        ByteArrayCodec.INSTANCE
    }
}

4 Configuring the Redis Lettuce Driver

Customizing The Redis Configuration

You can customize the Redis configuration using any properties exposed by the DefaultRedisConfiguration class. For example, in the application configuration file:

Customizing Redis Configuration
redis.uri=redis://localhost
redis.ssl=true
redis.timeout=30s
redis:
    uri: redis://localhost
    ssl: true
    timeout: 30s
[redis]
  uri="redis://localhost"
  ssl=true
  timeout="30s"
redis {
  uri = "redis://localhost"
  ssl = true
  timeout = "30s"
}
{
  redis {
    uri = "redis://localhost"
    ssl = true
    timeout = "30s"
  }
}
{
  "redis": {
    "uri": "redis://localhost",
    "ssl": true,
    "timeout": "30s"
  }
}

When redis.uri is present, you can still provide password authentication separately with either redis.authentication or redis.password, or embed credentials directly in the URI.

Configuring Lettuce Command Latency Metrics

If Micrometer is on the classpath, the Lettuce command latency recorder is enabled automatically. Histogram metrics remain enabled by default for compatibility, but you can now customize them in configuration:

Customizing Lettuce command latency metrics
redis.uri=redis://localhost
redis.metrics.command-latency-recorder.enabled=true
redis.metrics.command-latency-recorder.histogram=false
redis.metrics.command-latency-recorder.local-distinction=false
redis.metrics.command-latency-recorder.min-latency=2ms
redis.metrics.command-latency-recorder.max-latency=3m
redis.metrics.command-latency-recorder.target-percentiles[0]=0.25
redis.metrics.command-latency-recorder.target-percentiles[1]=0.75
redis:
    uri: redis://localhost
    metrics:
        command-latency-recorder:
            enabled: true
            histogram: false
            local-distinction: false
            min-latency: 2ms
            max-latency: 3m
            target-percentiles:
              - 0.25
              - 0.75
[redis]
  uri="redis://localhost"
  [redis.metrics]
    [redis.metrics.command-latency-recorder]
      enabled=true
      histogram=false
      local-distinction=false
      min-latency="2ms"
      max-latency="3m"
      target-percentiles=[
        0.25,
        0.75
      ]
redis {
  uri = "redis://localhost"
  metrics {
    commandLatencyRecorder {
      enabled = true
      histogram = false
      localDistinction = false
      minLatency = "2ms"
      maxLatency = "3m"
      targetPercentiles = [0.25, 0.75]
    }
  }
}
{
  redis {
    uri = "redis://localhost"
    metrics {
      command-latency-recorder {
        enabled = true
        histogram = false
        local-distinction = false
        min-latency = "2ms"
        max-latency = "3m"
        target-percentiles = [0.25, 0.75]
      }
    }
  }
}
{
  "redis": {
    "uri": "redis://localhost",
    "metrics": {
      "command-latency-recorder": {
        "enabled": true,
        "histogram": false,
        "local-distinction": false,
        "min-latency": "2ms",
        "max-latency": "3m",
        "target-percentiles": [0.25, 0.75]
      }
    }
  }
}

The same settings can be applied to named Redis connections under redis.servers.<name>.metrics.command-latency-recorder. Set enabled: false to disable the recorder for either the default or a named connection.

Multiple Redis Connections

You can configure multiple Redis connections using the redis.servers setting. For example:

Customizing Redis Configuration
redis.servers.foo.uri=redis://foo
redis.servers.bar.uri=redis://bar
redis:
    servers:
        foo:
            uri: redis://foo
        bar:
            uri: redis://bar
[redis]
  [redis.servers]
    [redis.servers.foo]
      uri="redis://foo"
    [redis.servers.bar]
      uri="redis://bar"
redis {
  servers {
    foo {
      uri = "redis://foo"
    }
    bar {
      uri = "redis://bar"
    }
  }
}
{
  redis {
    servers {
      foo {
        uri = "redis://foo"
      }
      bar {
        uri = "redis://bar"
      }
    }
  }
}
{
  "redis": {
    "servers": {
      "foo": {
        "uri": "redis://foo"
      },
      "bar": {
        "uri": "redis://bar"
      }
    }
  }
}

In which case the same beans will be created for each entry under redis.servers but exposed as @Named beans.

Using StatefulRedisConnection
@Inject @Named("foo") StatefulRedisConnection<String, String> connection;

The above example will inject the connection named foo.

MasterReplica Configuration

You can configure a standalone redis instance with replicas by supplying the redis.replica-uris setting to list out the location of all replicas.

MasterReplica Redis Configuration
redis:
    uri: redis://localhost
    replica-uris:
      - redis://localhost:6578
    ssl: true
    timeout: 30s

ReadFrom Settings

For MasterReplica and Cluster configurations the ReadFrom Setting can be configured using the redis.read-from setting.

ReadFrom Redis Configuration
redis:
    uris:
      - redis://localhost
    read-from: replicaPreferred

This setting accepts a string matching the values accepted in the ReadFrom.valueOf method. These are currently master, masterPreferred, upstream, upstreamPreferred, replica, replicaPreferred, lowestLatency , any, and anyReplica.

Named connection codec configuration

When using named redis connections, you can change the codec for each connection by supplying a named RedisCodec bean. For example:

Supply different codecs for named connections
@Factory
public class NamedCodecFactory {

    @Singleton
    @Named("foo")
    RedisCodec<byte[], byte[]> fooCodec() {
        return ByteArrayCodec.INSTANCE;
    }

    @Singleton
    @Named("bar")
    RedisCodec<String, String> barCodec() {
        return StringCodec.ASCII;
    }
}
Supply different codecs for named connections
@Factory
class NamedCodecFactory {

    @Singleton
    @Named("foo")
    fun fooCodec(): RedisCodec<ByteArray, ByteArray> = ByteArrayCodec.INSTANCE

    @Singleton
    @Named("bar")
    fun barCodec(): RedisCodec<String, String> = StringCodec.ASCII
}
Supply different codecs for named connections
@CompileStatic
@Factory
class NamedCodecFactory {

    @Singleton
    @Named("foo")
    RedisCodec<byte[], byte[]> fooCodec() {
        ByteArrayCodec.INSTANCE
    }

    @Singleton
    @Named("bar")
    RedisCodec<String, String> barCodec() {
        StringCodec.ASCII
    }
}

Redis Health Checks

When the redis-lettuce compatibility module or the focused redis-lettuce-core module is activated a RedisHealthIndicator is activated resulting in the /health endpoint and CurrentHealthStatus interface resolving the health of the Redis connection or connections.

The health indicator is enabled by default. To disable the health endpoint, you can do so via the config.

redis.health.enabled=false
redis:
   health:
    enabled: false
[redis]
  [redis.health]
    enabled=false
redis {
  health {
    enabled = false
  }
}
{
  redis {
    health {
      enabled = false
    }
  }
}
{
  "redis": {
    "health": {
      "enabled": false
    }
  }
}

By default, each health check opens a fresh Redis connection so the probe verifies new connections can still be established. If you prefer to reuse an existing managed Lettuce connection bean when available, enable redis.health.reuse-connection:

redis.health.reuse-connection=true
redis:
   health:
      reuse-connection: true
[redis]
  [redis.health]
    reuse-connection=true
redis {
  health {
    reuseConnection = true
  }
}
{
  redis {
    health {
      reuse-connection = true
    }
  }
}
{
  "redis": {
    "health": {
      "reuse-connection": true
    }
  }
}

See the section on the Health Endpoint for more information.

Disabling Redis

You can disable the creation of Redis connections using the redis.enabled setting, through configuration:

Disabling Redis
redis.enabled=false
redis:
    enabled: false
[redis]
  enabled=false
redis {
  enabled = false
}
{
  redis {
    enabled = false
  }
}
{
  "redis": {
    "enabled": false
  }
}

5 Redis and Testing

For testing purposes, we recommend running a real version of Redis inside a Docker container via TestContainers.

GenericContainer<?> redisContainer = new GenericContainer<>(DockerImageName.parse(REDIS_DOCKER_NAME))
    .withExposedPorts(REDIS_PORT)
    .waitingFor(
            Wait.forLogMessage(".*Ready to accept connections.*\\n", 1)
    );
redisContainer.start();

The embedded redis container we used to recommend has been deprecated as of 5.3.0 and will be removed at a later date.

6 Redis Pub/Sub Messaging

Redis Pub/Sub Messaging

Micronaut Redis Pub/Sub messaging is published as a dedicated module on top of the Lettuce integration.

The high-level Pub/Sub messaging integration is published in a dedicated module. Add it to your application alongside the base Lettuce module:

implementation("io.micronaut.redis:micronaut-redis-lettuce-pubsub")
<dependency>
    <groupId>io.micronaut.redis</groupId>
    <artifactId>micronaut-redis-lettuce-pubsub</artifactId>
</dependency>

To consume messages, annotate a bean with @RedisListener and annotate executable methods with @MessageChannel:

    @MessageChannel("books.created")
    void receive(@MessageBody BookCreated event, @MessageChannel String channel) {
        System.out.println("Received " + event.getTitle() + " from " + channel);
    }
    @MessageChannel("books.created")
    fun receive(@MessageBody event: BookCreated, @MessageChannel channel: String) {
        println("Received ${event.title} from $channel")
    }
    @MessageChannel("books.created")
    void receive(@MessageBody BookCreated event, @MessageChannel String channel) {
        println "Received ${event.title} from ${channel}"
    }

Listener bodies are decoded through Micronaut HTTP MessageBodyReader implementations. JSON is used by default, so richer message payloads work out of the box as long as a matching body reader is available.

To consume a different format for a particular channel, add @Consumes:

    @Consumes(MediaType.TEXT_PLAIN)
    @MessageChannel("books.plain-text")
    void receive(@MessageBody String title) {
        System.out.println("Received plain text title " + title);
    }
    @Consumes(MediaType.TEXT_PLAIN)
    @MessageChannel("books.plain-text")
    fun receive(@MessageBody title: String) {
        println("Received plain text title $title")
    }
    @Consumes(MediaType.TEXT_PLAIN)
    @MessageChannel("books.plain-text")
    void receive(@MessageBody String title) {
        println "Received plain text title ${title}"
    }

To publish messages imperatively, inject RedisPubSubPublisher and send the payload to the target channel:

    void publish(BookCreated event) {
        publisher.publish("books.created", event);
    }
    fun publish(event: BookCreated) {
        publisher.publish("books.created", event)
    }
    void publish(BookCreated event) {
        publisher.publish("books.created", event)
    }

For a declarative client API similar to Kafka, define an interface with @RedisPubSubClient and annotate methods with @MessageChannel:

    @MessageChannel("books.created")
    void publish(BookCreated event);
    @MessageChannel("books.created")
    fun publish(event: BookCreated)
    @MessageChannel("books.created")
    void publish(BookCreated event)

Client bodies are encoded through Micronaut HTTP MessageBodyWriter implementations. JSON is used by default, and @Produces lets you override the media type for a method:

    @MessageChannel("books.created.plain")
    @Produces(MediaType.TEXT_PLAIN)
    void publishPlain(BookCreated event);
    @MessageChannel("books.created.plain")
    @Produces(MediaType.TEXT_PLAIN)
    fun publishPlain(event: BookCreated)
    @MessageChannel("books.created.plain")
    @Produces(MediaType.TEXT_PLAIN)
    void publishPlain(BookCreated event)

@RedisPubSubClient methods can return void, a synchronous value, CompletionStage<T>, or a reactive Publisher<T>. For non-void results, Micronaut first converts the Redis subscriber count to T; if that conversion is not possible and T already matches the published body type, the original body is returned instead.

If you want a different default media type for all Pub/Sub payloads, configure it through redis.pubsub.default-body-media-type:

redis.pubsub.default-body-media-type=text/plain
redis:
  pubsub:
    default-body-media-type: text/plain
[redis]
  [redis.pubsub]
    default-body-media-type="text/plain"
redis {
  pubsub {
    defaultBodyMediaType = "text/plain"
  }
}
{
  redis {
    pubsub {
      default-body-media-type = "text/plain"
    }
  }
}
{
  "redis": {
    "pubsub": {
      "default-body-media-type": "text/plain"
    }
  }
}

Listener failures are delegated to RedisListenerExceptionHandler. The default handler logs the error, and you can select a specific handler per message channel:

    @MessageChannel(value = "books.audit", exceptionHandler = BookAuditExceptionHandler.class)
    void receive(@MessageBody BookCreated event) {
        throw new IllegalStateException("Could not audit " + event.getTitle());
    }
    @MessageChannel(value = "books.audit", exceptionHandler = BookAuditExceptionHandler::class)
    fun receive(@MessageBody event: BookCreated) {
        throw IllegalStateException("Could not audit ${event.title}")
    }
    @MessageChannel(value = "books.audit", exceptionHandler = BookAuditExceptionHandler)
    void receive(@MessageBody BookCreated event) {
        throw new IllegalStateException("Could not audit ${event.title}")
    }
    @Override
    public void handle(RedisListenerException exception) {
        System.err.println("Redis listener failure on " + exception.getMessageChannel() + ": " + exception.getMessage());
    }
    override fun handle(exception: RedisListenerException) {
        System.err.println("Redis listener failure on ${exception.messageChannel}: ${exception.message}")
    }
    @Override
    void handle(RedisListenerException exception) {
        System.err.println("Redis listener failure on ${exception.messageChannel}: ${exception.message}")
    }

Redis Pub/Sub listeners also participate in Micronaut graceful shutdown. During shutdown, the listener registry unsubscribes from Redis and waits for in-flight listener tasks to complete before finishing the graceful shutdown phase.

7 Redis for Caching

If you wish to use Redis to cache results then add the dedicated cache module to your classpath:

implementation("io.micronaut.redis:micronaut-redis-lettuce-cache")
<dependency>
    <groupId>io.micronaut.redis</groupId>
    <artifactId>micronaut-redis-lettuce-cache</artifactId>
</dependency>

The micronaut-redis-lettuce compatibility module also includes this functionality transitively.

Lettuce is a non-blocking, reactive Redis client implementation and Micronaut provides an implementation that allows cached results to be read reactively.

Within your application configuration configure the Redis URL and Redis caches:

Cache Configuration Example
redis.uri=redis://localhost
redis.cache.namespace=${micronaut.application.name}
redis.caches.my-cache.expire-after-write=1h
redis.caches.my-cache.read-retries=2
redis.caches.my-cache.insert-retries=2
redis:
    uri: redis://localhost
    cache:
        namespace: ${micronaut.application.name}
    caches:
        my-cache:
            expire-after-write: 1h
            read-retries: 2
            insert-retries: 2
[redis]
  uri="redis://localhost"
  [redis.cache]
    namespace="${micronaut.application.name}"
  [redis.caches]
    [redis.caches.my-cache]
      expire-after-write="1h"
      read-retries=2
      insert-retries=2
redis {
  uri = "redis://localhost"
  cache {
    namespace = "${micronaut.application.name}"
  }
  caches {
    myCache {
      expireAfterWrite = "1h"
      readRetries = 2
      insertRetries = 2
    }
  }
}
{
  redis {
    uri = "redis://localhost"
    cache {
      namespace = "${micronaut.application.name}"
    }
    caches {
      my-cache {
        expire-after-write = "1h"
        read-retries = 2
        insert-retries = 2
      }
    }
  }
}
{
  "redis": {
    "uri": "redis://localhost",
    "cache": {
      "namespace": "${micronaut.application.name}"
    },
    "caches": {
      "my-cache": {
        "expire-after-write": "1h",
        "read-retries": 2,
        "insert-retries": 2
      }
    }
  }
}
  • Set redis.cache.namespace to prefix cache keys when using the default key serializer, or override it per cache with redis.caches.<cache-name>.namespace. If you configure a custom redis.*.key-serializer, incorporate the namespace into the serialized key.

  • read-retries retries failed Redis cache reads before the cache operation fails.

  • insert-retries retries failed Redis cache writes before the cache operation fails.

Cache Configuration Example with a Dynamic Expiration policy
redis.uri=redis://localhost
redis.caches.my-cache.expiration-after-write-policy=<class path of class implementing ExpirationAfterWritePolicy>
redis:
    uri: redis://localhost
    caches:
        my-cache:
            expiration-after-write-policy: <class path of class implementing ExpirationAfterWritePolicy>
[redis]
  uri="redis://localhost"
  [redis.caches]
    [redis.caches.my-cache]
      expiration-after-write-policy="<class path of class implementing ExpirationAfterWritePolicy>"
redis {
  uri = "redis://localhost"
  caches {
    myCache {
      expirationAfterWritePolicy = "<class path of class implementing ExpirationAfterWritePolicy>"
    }
  }
}
{
  redis {
    uri = "redis://localhost"
    caches {
      my-cache {
        expiration-after-write-policy = "<class path of class implementing ExpirationAfterWritePolicy>"
      }
    }
  }
}
{
  "redis": {
    "uri": "redis://localhost",
    "caches": {
      "my-cache": {
        "expiration-after-write-policy": "<class path of class implementing ExpirationAfterWritePolicy>"
      }
    }
  }
}
🔗
Table 1. Configuration Properties for RedisCacheConfiguration
Property Type Description Default value

redis.caches.*.server

java.lang.String

Sets the name of the server to use.

redis.caches.*.value-serializer

java.lang.Class

Sets the {@link ObjectSerializer} to use for serializing values.

redis.caches.*.key-serializer

java.lang.Class

Sets the {@link ObjectSerializer} to use for serializing keys.

redis.caches.*.expire-after-write

java.time.Duration

The expiry to use after the value is written

redis.caches.*.expire-after-access

java.time.Duration

The {@link Duration}

redis.caches.*.expiration-after-write-policy

java.lang.String

The class path for an implementation of ExpirationAfterWritePolicy

redis.caches.*.namespace

java.lang.String

Sets the namespace to prefix cache keys with.

redis.caches.*.charset

java.nio.charset.Charset

The charset used to serialize and deserialize values

redis.caches.*.invalidate-scan-count

java.lang.Long

Sets the count used for the scan command in cache.RedisCache#invalidateAll(). See {@link io.lettuce.core.ScanArgs#limit(long)}. Defaults to 100L.

redis.caches.*.read-retries

java.lang.Integer

The number of times a failed cache read should be retried.

redis.caches.*.insert-retries

java.lang.Integer

The number of times a failed cache insert should be retried.

8 Session State with Redis

Storing Session instances in Redis requires special considerations.

Add the dedicated session module to enable Redis-backed session storage:

implementation("io.micronaut.redis:micronaut-redis-lettuce-session")
<dependency>
    <groupId>io.micronaut.redis</groupId>
    <artifactId>micronaut-redis-lettuce-session</artifactId>
</dependency>

The micronaut-redis-lettuce compatibility module also includes this functionality transitively.

You can configure how sessions are stored in Redis using RedisHttpSessionConfiguration.

The following represents an example configuration in the application configuration file:

Configuring Redis Sessions
micronaut.session.http.redis.enabled=true
micronaut.session.http.redis.namespace=myapp:sessions
micronaut.session.http.redis.write-mode=BACKGROUND
micronaut.session.http.redis.enable-keyspace-events=false
micronaut:
    session:
        http:
            redis:
                enabled: true
                namespace: 'myapp:sessions'
                write-mode: BACKGROUND
                enable-keyspace-events: false
[micronaut]
  [micronaut.session]
    [micronaut.session.http]
      [micronaut.session.http.redis]
        enabled=true
        namespace="myapp:sessions"
        write-mode="BACKGROUND"
        enable-keyspace-events=false
micronaut {
  session {
    http {
      redis {
        enabled = true
        namespace = "myapp:sessions"
        writeMode = "BACKGROUND"
        enableKeyspaceEvents = false
      }
    }
  }
}
{
  micronaut {
    session {
      http {
        redis {
          enabled = true
          namespace = "myapp:sessions"
          write-mode = "BACKGROUND"
          enable-keyspace-events = false
        }
      }
    }
  }
}
{
  "micronaut": {
    "session": {
      "http": {
        "redis": {
          "enabled": true,
          "namespace": "myapp:sessions",
          "write-mode": "BACKGROUND",
          "enable-keyspace-events": false
        }
      }
    }
  }
}
  • The Redis namespace spcifies where to write sessions.

  • Using BACKGROUND write sessions changes in the background

  • enable-keyspace-events enables/disables programmatic activation of keyspace events

The RedisSessionStore implementation uses keyspace events to cleanup active sessions and fire SessionExpiredEvent and requires they are active.

By default sessions values are serialized using Java serialization and stored in Redis hashes. You can configure serialization to instead use Jackson to serialize to JSON if desired:

Using Jackson Serialization
micronaut.session.http.redis.enabled=true
micronaut.session.http.redis.valueSerializer=io.micronaut.jackson.serialize.JacksonObjectSerializer
micronaut:
    session:
        http:
            redis:
                enabled: true
                valueSerializer: io.micronaut.jackson.serialize.JacksonObjectSerializer
[micronaut]
  [micronaut.session]
    [micronaut.session.http]
      [micronaut.session.http.redis]
        enabled=true
        valueSerializer="io.micronaut.jackson.serialize.JacksonObjectSerializer"
micronaut {
  session {
    http {
      redis {
        enabled = true
        valueSerializer = "io.micronaut.jackson.serialize.JacksonObjectSerializer"
      }
    }
  }
}
{
  micronaut {
    session {
      http {
        redis {
          enabled = true
          valueSerializer = "io.micronaut.jackson.serialize.JacksonObjectSerializer"
        }
      }
    }
  }
}
{
  "micronaut": {
    "session": {
      "http": {
        "redis": {
          "enabled": true,
          "valueSerializer": "io.micronaut.jackson.serialize.JacksonObjectSerializer"
        }
      }
    }
  }
}

9 GraalVM support

It is possible to create native images for Micronaut applications that use the Lettuce driver. There are some limitations and configuration needed because of the driver itself so please make sure you read the official driver documentation about GraalVM. Micronaut provides the configuration for Netty so you don’t need to add that part to your own reflect-config.json.

See the section on GraalVM in the user guide for more information.

10 Repository

You can find the source code of this project in this repository:

11 Appendices

11.1 Breaking Changes

This section documents breaking changes between versions

7.0.0

The following were deprecated in version 6.0.0 and have been removed.

  • Singleton constructor: RedisHealthIndicator(BeanContext, HealthAggregator, RedisClient[], RedisClusterClient[]). It is replaced with RedisHealthIndicator(BeanContext, ExecutorService, HealthAggregator<?>, RedisClient[], RedisClusterClient[]).

  • Factory method: DefaultRedisClusterClientFactory.redisClient(AbstractRedisConfiguration, ClientResources). It is replaced with redisClient(AbstractRedisConfiguration, ClientResources, List<ClientResourcesMutator>).

5.3.0

  • The embedded Redis server that can be used for testing has been changed to only bind to localhost.

If you wish to revert to the previous behavior, you will need to use a configuration file specified in your test specific application configuration file.

embedded-redis.conf
maxmemory 256M
redis.embedded.config-file=/full/path/to/embedded-redis.conf
redis:
  embedded:
    config-file: '/full/path/to/embedded-redis.conf'
[redis]
  [redis.embedded]
    config-file="/full/path/to/embedded-redis.conf"
redis {
  embedded {
    configFile = "/full/path/to/embedded-redis.conf"
  }
}
{
  redis {
    embedded {
      config-file = "/full/path/to/embedded-redis.conf"
    }
  }
}
{
  "redis": {
    "embedded": {
      "config-file": "/full/path/to/embedded-redis.conf"
    }
  }
}