What Are Redis Keyspace Notifications?

Redis keyspace notifications are Pub/Sub events that the server publishes whenever a command modifies a key, so a client can react to changes in the data set instead of polling it. Subscribe to the right channel and a message arrives every time a key is set, deleted, renamed, expired or evicted.

They are disabled by default, and the documentation is direct about why: "By default keyspace event notifications are disabled because while not very sensible the feature uses some CPU power." Turning them on is one configuration line. Understanding what that line does — and what the resulting events do not promise — is where the difficulty lives.

Keyspace vs. Keyevent: The Two Channel Families

A notified operation publishes to two channel families, carrying inverted information. The documentation puts it as an equivalence — with both families enabled, a DEL against the key mykey in database 0 triggers "the delivering of two messages, exactly equivalent to the following two PUBLISH commands":

PUBLISH __keyspace@0__:mykey del
PUBLISH __keyevent@0__:del mykey

That inversion is the whole design. The channel is named after one half and carries the other:

One command, two channels, inverted payloads DEL mykey database 0 flag K named after the KEY __keyspace@0__:mykey payload del what happened to this key flag E named after the EVENT __keyevent@0__:del payload mykey which key had this event

Use keyspace channels to watch a small, known set of keys — a session, a lock, one cache entry. Use keyevent channels to watch a class of event across the database, which is why almost every "do something when a key expires" design subscribes to __keyevent@0__:expired. The @0 is the database number and part of the channel name, so covering several databases needs a pattern subscription.

Both are ordinary Redis Pub/Sub channels — no separate protocol, no special client mode. SUBSCRIBE and PSUBSCRIBE behave exactly as they do anywhere else, and the delivery guarantees are Pub/Sub's guarantees. Cluster routing is the one place keyspace notifications diverge from ordinary Pub/Sub, and that is covered in the guarantees section below.

Enabling Notifications: The notify-keyspace-events Flags

Notifications are switched on with the notify-keyspace-events parameter, in redis.conf or at runtime with CONFIG SET. The empty string — the default — disables them.

# redis.conf
notify-keyspace-events "Ex"

# or, without a restart
CONFIG SET notify-keyspace-events Ex

The value is not a word but a set of single-character flags, each either a channel selector or an event class selector.

FlagMeaning
KKeyspace events, published with the __keyspace@<db>__ prefix
EKeyevent events, published with the __keyevent@<db>__ prefix
gGeneric, non-type-specific commands: DEL, EXPIRE, RENAME, …
$ l s h z t aString, list, set, hash, sorted-set, stream and array commands
dModule key type events
xExpired events
eEvicted events, from the maxmemory policy
mKey-miss events — accessing a key that does not exist (not in A)
nNew-key events (not in A)
oOverwritten events (not in A)
cType-changed events (not in A)
S T I VThe four subkey channel families, added in Redis 8.8 (not in A) — see below
AAlias for g$lshztdxea

One rule catches people before any of the rest matters: "At least K or E should be present in the string, otherwise no event will be delivered regardless of the rest of the string." A configuration of Ex works. A configuration of x alone is valid, accepted without complaint, and delivers nothing.

Why KEA Does Not Send Everything

KEA is repeated everywhere as the way to receive every notification. It is not. A expands to g$lshztdxea, and the documentation is precise about the consequence: "the AKE string means all the events except m, n, o and c." The docs' own summary line says KEA enables "most types of events" — not all of them.

The omission that bites hardest is n. "Notify me when a key is created" is one of the most common reasons to reach for this feature, and under KEA the new event never fires — no error, no warning. Add the flag explicitly:

CONFIG SET notify-keyspace-events KEAn

The same is true of the four subkey flags added in 8.8: A does not include those either, so KEA delivers no subkey notifications.

Mind the version history: new arrived in Redis 7.0 and key-miss events in Redis 6.0. An unrecognised flag character is rejected rather than ignored, so KEAn fails outright on a 6.x server instead of silently doing nothing — the opposite of the x-without-K-or-E case above, where every character is valid and the result is merely silence.

Redis and Valkey Flag Tables Have Diverged

Since the fork, Redis and Valkey have drifted apart on this feature. Anyone running both, or migrating between them, is working from a config string that does not mean the same thing on each side:

RedisValkey
A expands tog$lshztdxeag$lshztxed
A excludesm, n, o, cm, n
a — array commandsYesNo
o — overwrittenYesNo
c — type-changedYesNo
Subkey notifications — S T I VYes, since Redis 8.8No — the characters are not recognised
Hash-field expiry eventsYesYes, since Valkey 9.0

Two consequences. A Valkey deployment cannot emit overwritten or type_changed events at all, so code subscribing to them sits idle rather than failing. And because Valkey does not recognise S, T, I or V, a subkey configuration copied from Redis is not merely inert there — it is rejected, and the CONFIG SET fails. Channel names, the K/E model, expired and evicted events and the delivery guarantees are the same on both.

One divergence you may read about is not real. The Valkey documentation states that a list move delivers the lpush event after the rpop event, the reverse of what the Redis documentation says. Both implementations publish the push first; that line in the Valkey docs does not match its own code. Do not rewrite a consumer around it.

What Keyspace Notifications Guarantee — and What They Don't

Because notifications are published over Pub/Sub, they inherit its delivery model exactly, and the documentation states it in the first note on the page: "Redis Pub/Sub is fire and forget; that is, if your Pub/Sub client disconnects, and reconnects later, all the events delivered during the time the client was disconnected are lost."

That sentence rules out a long list of designs. There is no acknowledgment, no redelivery, no dead-letter path and no way to ask for events you missed. A failover, a rolling restart, a GC pause long enough to drop the connection — each is a permanent hole in the stream, and nothing on the server records that the hole exists.

What people assumeWhat actually happens
Events are queued while I reconnectThey are dropped. There is no buffer and no replay.
A cluster broadcasts events to every node"[U]nlike regular Pub/Sub communication in a cluster, events' notifications are not broadcasted to all nodes." Each node publishes only for the keys it owns, so a client must subscribe to every node.
Every command I run produces an event"[A]ll the commands generate events only if the target key is really modified." An SREM for an element that is not in the set is silent.
Enabling everything is freeThe feature "uses some CPU power", which is why it ships disabled. Every notified write becomes at least one extra publish, and more as the flag string widens — a single HSET under KE plus the subkey channels produces several.

The cluster row is the most common cause of "my listener works locally and not in production": a client connected to one primary in a six-shard cluster sees roughly a sixth of the events, with no error to indicate the rest exist.

Why Expired-Key Events Arrive Late

The expired event is the most-used notification and the most misunderstood, because it does not fire when a TTL reaches zero. Redis removes expired keys two ways: lazily, when a command touches a key and finds it expired, and actively, through a background cycle that samples keys incrementally.

The event follows the deletion, not the deadline: "Expired (expired) events are generated when the Redis server deletes the key and not when the time to live theoretically reaches the value of zero." And the gap is not bounded — "if no command targets the key constantly, and there are many keys with a TTL associated, there can be a significant delay between the time the key time to live drops to zero, and the time the expired event is generated."

So keyspace notifications are not a scheduler. A payment retry, session cleanup or delayed job triggered at the moment a TTL lapses will run late by an unpredictable margin once the instance holds a large population of keys with TTLs that nothing reads. If the timing matters, drive it from a sorted set of due timestamps or from Redis Streams, and treat the expired event as a hint. See Redis TTL for how expiration itself works.

Eviction is a separate event with a separate flag: keys dropped to satisfy maxmemory generate evicted (flag e), not expired (flag x) — worth keeping straight when a cache starts losing entries and the question is whether the cause is a TTL or the eviction policy.

Subkey Notifications in Redis 8.8

Standard notifications work at key granularity, which is a real limitation on hashes: an HSET against a 500-field hash tells you the hash changed and nothing about which field. Redis 8.8 added subkey notifications to close that gap, carrying the affected field names in the payload, on four new channel families:

ChannelPayloadSubscribe when you want…
__subkeyspace@<db>__:<key><event>|<len>:<subkey>[,...]one key, any field
__subkeyevent@<db>__:<event><key_len>:<key>|<len>:<subkey>[,...]one event type, any key
__subkeyspaceitem@<db>__:<key>\n<subkey><event>one specific field of one key
__subkeyspaceevent@<db>__:<event>|<key><len>:<subkey>[,...]one event on one key

Each family has its own flag — S, T, I and V respectively — and these are "independent from the existing key-level flags (K, E, and so on)". Enabling subkey notifications does not implicitly enable standard ones, or the reverse.

They are not independent of the data-type flags, though, and that is the trap. A subkey event is published only when the configuration contains both a channel-class flag (S, T, I or V) and the type flag for the command that fired it — today, h for hashes. That is why h appears in the same flag list as S, T, I and V in the documentation. It is also why the documentation's own worked example, which sets ST with no h, produces nothing at all. Include it:

$ redis-cli config set notify-keyspace-events hST
$ redis-cli --csv psubscribe '__subkey*'

# in another terminal: redis-cli hset myhash field1 val1 field2 val2

"pmessage","__subkey*","__subkeyspace@0__:myhash","hset|6:field1,6:field2"
"pmessage","__subkey*","__subkeyevent@0__:hset","6:myhash|6:field1,6:field2"

The number before each colon is the byte length of the name that follows, which is what makes the payload safe to parse when a field name contains a comma or a pipe. Read the published example carefully if you are writing a parser against it: it prints 7:field1 and 5:myhash for two names that are both six bytes long, so those figures are typos rather than a different encoding.

Scope is currently narrow: "only hash commands are supported; support for additional data types is planned for future releases." The emitting commands, all of them operating on a Redis hash, are HSET, HMSET, HSETNX, HDEL, HGETDEL, HGETEX, HSETEX, HINCRBY, HINCRBYFLOAT, HPERSIST, the HEXPIRE family, and field expiration itself, which batches all expired fields into one hexpired notification. Being Pub/Sub, subkey notifications are fire-and-forget on the same terms as everything above.

Keyspace Notifications in Java With Redisson

There are two ways to consume these events from Java. The first is to treat them as what they are — Pub/Sub messages — and subscribe to the channel pattern yourself with Redisson's RPatternTopic:

RPatternTopic topic = redisson.getPatternTopic("__keyevent@0__:expired");

topic.addListener(String.class,
    (CharSequence pattern, CharSequence channel, String key) -> onExpired(key));

That is roughly what every client library offers. The second route is typed object listeners, which drop the channel-name string handling and bind the callback to the object itself:

RBucket<String> bucket = redisson.getBucket("session:8f21");

int expiredId = bucket.addListener((ExpiredObjectListener) name -> evict(name));
int deletedId = bucket.addListener((DeletedObjectListener) name -> evict(name));

bucket.removeListener(expiredId);
bucket.removeListener(deletedId);

Each listener depends on the server being configured for the events it needs, and this mapping is the thing most often missed. A listener whose flags are wrong produces no error — it produces silence:

ListenerFires onRequired notify-keyspace-events
TrackingListenerData created or updated after a read— (uses client-side tracking, not notifications)
SetObjectListenerData created or updatedE$
ExpiredObjectListenerObject expiredEx
DeletedObjectListenerObject deletedEgx — see below

That last row differs from Redisson's own reference table, which lists Ex, and it is a good illustration of why the mapping matters. DeletedObjectListener subscribes to __keyevent@*:del, and del is a generic-class event — flag g, the class covering DEL, EXPIRE and RENAME — not an expired-class event. Under Ex alone, deletions never arrive. Use Egx, or EA and let the alias cover both classes.

Redisson's local cache configurations expose the same choice through ExpirationEventPolicy, which decides how an instance learns its own key was removed — DONT_SUBSCRIBE, SUBSCRIBE_WITH_KEYEVENT_PATTERN (the __keyevent@*:expired pattern) or SUBSCRIBE_WITH_KEYSPACE_CHANNEL (the __keyspace@N__:name channel).

The per-node cluster problem described above is handled for you: Redisson subscribes across the nodes of a Redis Cluster and re-subscribes after a reconnection or failover, so a listener registered once keeps receiving events from whichever node owns the key. Re-subscription restores the subscription, not the events missed while the connection was down — that limit is in the protocol, and no client can remove it. Where losing an event is unacceptable, Redisson PRO offers Reliable PubSub, which adds acknowledgment, redelivery, dead-letter topics and replay; it can be evaluated with a free trial.

RMapCache Listeners Are Not Keyspace Notifications

The two are easy to conflate. RMapCache and RSetCache entry listeners do not come from the server: those types are Lua-backed, and their per-entry expiry is handled by a Redisson eviction task rather than by Redis — see Redis TTL for what that costs. Object listeners on RBucket are the ones driven by notify-keyspace-events.

When to Use Notifications — and When You Want Streams

Keyspace notifications occupy a narrow niche, and two neighbouring features cover the cases they handle badly.

MechanismUse it forDelivery
Client-side caching (tracking)Invalidating a local cache when a key it holds changesServer tracks what each client read; no flags to configure
Keyspace notificationsReacting to changes across keys you do not have to read first — expiry, eviction, external writersAt-most-once, no replay
Redis StreamsAnything where a missed event is a bugPersistent log, consumer groups, per-message acknowledgment, replay

A useful test: if the right response to a dropped event is "we will catch it on the next one", notifications are the correct tool and the cheapest one. If it is "we have to go back and process it", the event belongs in a stream, and the notification — if used at all — should only trigger a read of durable state. This is the same boundary that separates Pub/Sub from a message queue, and the same reason event-driven architecture treats the log as the source of truth rather than the notification. For the cache-specific version of the decision, see cache invalidation.

Redis Keyspace Notifications: Frequently Asked Questions

How Do I Enable Keyspace Notifications in Redis?

Set the notify-keyspace-events parameter, either in redis.conf or at runtime with CONFIG SET notify-keyspace-events Ex. The string is a set of single-character flags. At least K or E must be present, or nothing is delivered no matter what else the string contains. The default is the empty string, which disables the feature.

Does KEA Enable All Keyspace Notifications?

No. A is an alias for g$lshztdxea, so KEA means every event except key-miss (m), new-key (n), overwritten (o) and type-changed (c). It does not include the subkey flags added in Redis 8.8 either, so KEA delivers no subkey notifications. The Redis documentation describes KEA as enabling "most types of events". To receive new-key events as well, use KEAn.

Why Is My Redis Expired Event Not Firing?

Three causes account for almost all of it. The configuration may be missing K or Ex alone delivers nothing. The event may be late rather than missing, because it is generated when the server actually deletes the key, not when the TTL reaches zero. Or the server is a cluster and the client is subscribed to one node, which only publishes events for the keys it owns.

Do Keyspace Notifications Work in Redis Cluster?

Yes, but not the way regular Pub/Sub does. Each node generates events for its own subset of the keyspace and, unlike regular Pub/Sub messages, those notifications are not broadcast to the other nodes. To see all events in a cluster, a client must hold a subscription to every node. Client libraries that manage this for you, such as Redisson, hide the difference; a hand-rolled single-connection subscriber will silently receive only one shard's worth.

Are Redis Keyspace Notifications Reliable?

No — they are at-most-once. Notifications ride on Pub/Sub, which is fire and forget: if a subscriber disconnects and reconnects, every event published while it was away is lost, with no acknowledgment, redelivery or replay. For workloads where a missed event is a defect, use Redis Streams, which persist entries and support consumer groups and per-message acknowledgment.

What Is the Difference Between __keyspace@ and __keyevent@?

They carry the same information inverted. A keyspace channel is named after the key and its message is the event name, so __keyspace@0__:mykey receives del. A keyevent channel is named after the event and its message is the key name, so __keyevent@0__:del receives mykey. Subscribe to keyspace channels to watch specific keys and to keyevent channels to watch a class of event across the database.

What Are Subkey Notifications in Redis 8.8?

Subkey notifications, added in Redis 8.8, extend keyspace notifications down to individual elements within a key — currently hash fields only. They add four channel families (__subkeyspace@, __subkeyevent@, __subkeyspaceitem@ and __subkeyspaceevent@) enabled by the flags S, T, I and V. Those flags are independent of K and E, so enabling one system does not enable the other. They are not independent of the data-type flag, however: a hash subkey event also requires h, so the documentation's own example value of ST delivers nothing. Use hST.

Do Valkey and Redis Support the Same Notification Flags?

Mostly, but not entirely. Both use the same channel names and the same K/E model. Redis expands A to g$lshztdxea and excludes m, n, o and c; Valkey expands it to g$lshztxed and excludes only m and n, because it has no array-command, overwritten or type_changed events at all. The larger gap is that Valkey does not recognise the subkey flags S, T, I and V, so a Redis 8.8 subkey configuration is rejected outright there.

Similar terms