The Redis API
The Redis global gives your scripts a pooled connection (via Redisson) to a Redis server: string/hash
key-value storage, lists, atomic counters, and pub/sub. Unlike
GlobalMap and
GlobalNotification, which only work within a single
JVM, Redis is the tool for state and messaging that has to cross multiple servers in a network.
The module is optional and disabled by default. Configuration lives in redis.yml. See
Configuration.
Checking availability
Redis.isAvailable() tells you whether enabled: true is set and the connection is up. Guard any script
that has a hard dependency on it:
if (!Redis.isAvailable()) {
throw new Error('Redis is not available');
}
Throwing at the top level marks the script as failed and shows up in /script list. Do not silently
degrade a feature that needs cross-server state into one that only half-works per-server.
Key/value operations
Redis.set(key, value) / Redis.get(key)
Plain string storage.
Redis.set('server:lobby-1:motd', 'Welcome!');
const motd = Redis.get('server:lobby-1:motd'); // null if the key doesn't exist
Redis.setEx(key, value, seconds)
Same as set, but the key expires itself after seconds. The natural fit for cooldowns, temporary bans, or
one-time tokens, without you having to clean anything up.
Redis.setEx(`cooldown:${uuid}:teleport`, 'true', 30);
Redis.exists(key) / Redis.delete(key)
if (Redis.exists(`ban:${uuid}`)) {
player.kickPlayer('You are banned network-wide.');
}
Redis.delete(`cooldown:${uuid}:teleport`); // true if a key was actually removed
Hash operations
A hash is a map of fields under one key, good for structured per-entity data (a player’s stats, an item’s metadata) without exploding into one Redis key per field.
Redis.hset(`player:${uuid}:stats`, 'kills', String(kills));
Redis.hset(`player:${uuid}:stats`, 'deaths', String(deaths));
const kills = Redis.hget(`player:${uuid}:stats`, 'kills');
const stats = Redis.hgetAll(`player:${uuid}:stats`); // { kills: '12', deaths: '4' }
Values are strings on the wire, same as set/get. Parse numbers yourself with Number(...).
Atomic counters
Redis.increment(key) and Redis.decrement(key) are atomic across every server connected to the same
Redis instance, which makes them the right tool for currency, cross-server unique IDs, or shared rate
limits, where two servers writing to the same key from get/set would race.
const newBalance = Redis.increment(`balance:${uuid}`);
const remaining = Redis.decrement(`votes-remaining:${uuid}`);
List operations
A Redis list is an ordered collection under one key, visible to every connected server. The natural fit for cross-server queues and small ordered state that more than one server reads or writes.
Redis.listPush(`queue:${uuid}:homes`, 'spawn'); // append to the end
const homes = Redis.listGet(`queue:${uuid}:homes`); // the whole list, in order
Redis.listContains(`queue:${uuid}:homes`, 'spawn'); // true
Redis.listRemove(`queue:${uuid}:homes`, 'spawn'); // drop every occurrence
Redis.listGet(key)
Returns the entire list, in order. An empty list if the key does not exist. It supports index access and
iteration like an array; call toArray() if you want a real JS array to run array methods on.
Redis.listPush(key, value)
Appends value to the end of the list. The list is created on the first push, so there is no explicit
setup step.
Redis.listSet(key, values)
Replaces the entire list in one call. The old contents are deleted first, which makes this how you write a modified list back:
const homes = Redis.listGet(`queue:${uuid}:homes`);
Redis.listSet(`queue:${uuid}:homes`, homes.toArray().filter((home) => home !== 'old-base'));
Redis.listRemove(key, value)
Removes every occurrence of value from the list.
Redis.listContains(key, value)
Returns true if value is in the list.
Unlike the hash getters, every list helper touches the whole list per call, so they are O(n). Keep lists
for small, ordered data; a large collection you would otherwise query belongs in
Sql.
Pub/Sub
Redis.publish and Redis.subscribe broadcast between every server connected to the same Redis instance,
which is what makes this the cross-server counterpart to
GlobalNotification (single JVM only).
Redis.publish(channel, message)
Returns the number of subscribers that received the message, across every connected server.
Redis.publish('chat:global', JSON.stringify({ player: sender.getName(), text: message }));
Redis.subscribe(channel, handler)
handler is (channel, message) => void. Returns a listener id, needed to unsubscribe.
const listenerId = Redis.subscribe('chat:global', (channel, message) => {
const { player, text } = JSON.parse(message);
Bukkit.broadcast(`<${player}> ${text}`);
});
Subscriptions registered this way are automatically cleaned up when the script unloads, same guarantee
GlobalNotification gives you. You do not need an unload callback for this. Unsubscribe manually only if
you need to stop listening before that:
Redis.unsubscribe('chat:global', listenerId);
Worked example: cross-server chat
// features/chat/relay.js
if (!Redis.isAvailable()) throw new Error('Redis is not available');
const SERVER_NAME = 'lobby-1';
Redis.subscribe('chat:global', (channel, message) => {
const payload = JSON.parse(message);
if (payload.origin === SERVER_NAME) return; // don't echo our own messages back
Bukkit.broadcast(`<gray>[${payload.origin}]</gray> <${payload.player}> ${payload.text}`);
});
registerListener($.AsyncChatEvent, (event) => {
Redis.publish('chat:global', JSON.stringify({
origin: SERVER_NAME,
player: event.getPlayer().getName(),
text: event.message(),
}));
});
Best practices
Do
- Guard the module with
Redis.isAvailable()at load time - Use
increment/decrementinstead of read-modify-write for anything two servers could touch at once - Use list operations for small, ordered, cross-server collections and queues
- Namespace keys by feature and entity, e.g.
feature:entity-id:field - Use
setExfor anything that should expire instead of tracking expiry yourself
Do not
- Assume
get/hgetsucceeded without checking fornull - Treat list operations as a queryable structure; they read and rewrite the whole list per call
- Treat
Redisas a database for data that needs querying, filtering or joins, that is whatSqlis for - Store large blobs; Redis is for hot, small, frequently accessed state
Redis versus the other shared-state globals
GlobalMap/GlobalNotificationfor state and messaging within a single server process. No network hop, no external dependency, but invisible to every other server in the network.Redisfor the same shapes of problem the moment more than one server needs to see them.Sqlfor data that needs to be queried, filtered, or joined, not just fetched by key.
See Shared state and messaging for the single-JVM globals.