Redis Sorted Sets in Java: ZADD, ZRANGE, ZRANGEBYSCORE, and ZREMRANGEBYSCORE with Redisson

Published on
July 23, 2026

Published on July 17, 2026

Whenever a Java application needs to rank, score, or order data — leaderboards, priority queues, rate limiters, or time-ordered feeds — the Redis sorted set is usually the right tool for the job. A sorted set keeps unique members ordered by a numeric score at all times, so you can read the top N members, or every member inside a score range, without scanning or sorting anything yourself.

The catch for Java developers is that the sorted set commands — ZADD, ZRANGE, ZRANGEBYSCORE, ZREMRANGEBYSCORE, and the rest — are designed for the Valkey or Redis command line, not for Java. In this guide, we'll walk through each of the core commands and show its type-safe equivalent in Redisson, the Valkey and Redis Java client, using the RScoredSortedSet interface. Every example is runnable code you can drop into a project.

What Is a Redis Sorted Set?

A Redis sorted set (known internally as a "ZSET") stores a collection of unique members, each paired with a floating-point score. Members can't repeat, but Redis keeps them permanently ordered by their score, breaking ties lexicographically. That ordering is what makes sorted sets so fast for ranking use cases: retrieving a member's rank, or a slice of the ranking, is close to instant because the data never needs re-sorting.

For a deeper conceptual explanation, see our glossary entry on the Redis sorted set. In this article, we'll focus on the commands and their Java equivalents.

Setting Up RScoredSortedSet in Redisson

First, add the Redisson dependency to your project. If you're using Maven, add the following to your pom.xml (check for the latest version before you build):

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>4.6.1</version>
</dependency>

Next, configure Redisson to connect to your data store and obtain an RScoredSortedSet. The connection URL is identical whether you're running Valkey or Redis:

Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);

RScoredSortedSet<String> board = redisson.getScoredSortedSet("game:leaderboard");

The board object is now a distributed sorted set. Everything below operates on it.

ZADD: Adding and Updating Members in Java

The ZADD command adds one or more members with their scores, or updates the score of members that already exist. On the command line it looks like this:

zadd game:leaderboard 1500 player:alice 1800 player:bob

ZADD also accepts flags that change its behavior: NX (only add new members), XX (only update existing ones), GT and LT (only update if the new score is greater or less than the current one), CH (return the count of changed members), and INCR (increment instead of replace). Redisson exposes each of these as a dedicated method, so you never have to assemble raw flags:

// Basic ZADD: set or overwrite a member's score
board.add(1500, "player:alice");

// ZADD with several members at once
Map<String, Double> scores = new HashMap<>();
scores.put("player:bob", 1800.0);
scores.put("player:carol", 1650.0);
board.addAll(scores);

// ZADD INCR: atomically add to a score and return the new total
Double newScore = board.addScore("player:bob", 50);

// ZADD INCR plus rank, in a single round trip (0 = top, reversed)
Integer rank = board.addScoreAndGetRevRank("player:carol", 100);

// Conditional writes: NX, XX, GT, LT
board.addIfAbsent(1000, "player:dave");   // NX: only if not already present
board.addIfExists(2000, "player:alice");  // XX: only if already present
board.addIfGreater(1900, "player:bob");   // GT: only if the new score is higher
board.addIfLess(1200, "player:carol");    // LT: only if the new score is lower

The addScore method is fully atomic. If dozens of servers award points to the same member at the same moment, Redis applies each increment in sequence — no points are lost to race conditions. The addScoreAndGetRevRank method goes further, combining the write and a rank lookup into one network call, which is ideal for instantly telling a user their new position.

ZRANGE: Reading Members by Rank in Java

ZRANGE returns members by their index (rank) position, ordered from the lowest score to the highest. Adding REV reverses the order, and WITHSCORES includes each member's score:

zrange game:leaderboard 0 2 withscores

In Redisson, valueRange returns just the members, while entryRange returns members paired with their scores as ScoredEntry objects. Each has a reversed variant for reading the top of the board first:

// ZRANGE 0 2 — the three lowest-scored members, ascending
Collection<String> bottomThree = board.valueRange(0, 2);

// ZREVRANGE 0 2 — the three highest-scored members, descending
Collection<String> topThree = board.valueRangeReversed(0, 2);

// ...WITHSCORES — read members together with their scores
Collection<ScoredEntry<String>> topWithScores = board.entryRangeReversed(0, 2);
for (ScoredEntry<String> entry : topWithScores) {
    System.out.println(entry.getValue() + " => " + entry.getScore());
}

Use valueRange when you only need the members, and entryRange when you need the scores too — it saves bandwidth to skip the scores when the UI doesn't display them.

ZRANGEBYSCORE: Querying Members by Score in Java

Where ZRANGE works by rank, ZRANGEBYSCORE works by score, returning every member whose score falls between a minimum and a maximum. It supports exclusive bounds (with a leading parenthesis), the infinity markers -inf and +inf, and a LIMIT offset count clause for pagination:

zrangebyscore game:leaderboard 1500 2000

Redisson overloads valueRange and entryRange with score-based signatures. Each score bound takes a companion boolean that controls whether the bound is inclusive:

// ZRANGEBYSCORE 1500 2000 — inclusive score band
Collection<String> midTier = board.valueRange(1500, true, 2000, true);

// ZRANGEBYSCORE (1500 (2000 — exclusive bounds
Collection<String> strictMid = board.valueRange(1500, false, 2000, false);

// WITHSCORES plus LIMIT offset count — a paged slice of the band
Collection<ScoredEntry<String>> page =
        board.entryRange(1500, true, 2000, true, 0, 10);

// ZCOUNT 1500 2000 — how many members sit inside the band
int inBand = board.count(1500, true, 2000, true);

This is the workhorse for "everyone between X and Y" queries: users in a score tier, events in a time window, or items in a price bracket.

ZREMRANGEBYSCORE: Removing Members by Score in Java

ZREMRANGEBYSCORE deletes every member whose score falls within a range and returns the number removed. It's the standard way to trim a sorted set — dropping stale entries, expiring old events, or pruning a sliding window. Its sibling, ZREMRANGEBYRANK, does the same thing by index position instead of score:

zremrangebyscore game:leaderboard -inf 1000

Redisson maps both directly:

// ZREMRANGEBYSCORE -inf 1000 — remove everyone scoring 1000 or below
int removedByScore =
        board.removeRangeByScore(Double.NEGATIVE_INFINITY, true, 1000, true);

// ZREMRANGEBYRANK 0 4 — remove the five lowest-ranked members by index
int removedByRank = board.removeRangeByRank(0, 4);

Pair this with a time-to-live when you want a sorted set to clean itself up. Because RScoredSortedSet is expirable, you can set a TTL so that per-window keys (a daily board, an hourly rate-limit bucket) delete themselves automatically:

board.expire(Duration.ofDays(2));   // auto-delete two days later

Sorted Set Command Reference: Redis to Redisson

Here's how the most common sorted set commands map to Redisson methods:

Redis / Valkey commandPurposeRedisson method
ZADDAdd or update members with scoresadd, addAll, addScore, addIfAbsent, addIfExists, addIfGreater, addIfLess
ZRANGE / ZREVRANGERead members by rankvalueRange(int, int), entryRange(int, int), and reversed variants
ZRANGEBYSCORERead members by score rangevalueRange(double, boolean, double, boolean), entryRange(...)
ZREMRANGEBYSCORERemove members by score rangeremoveRangeByScore
ZREMRANGEBYRANKRemove members by rank rangeremoveRangeByRank
ZSCOREGet a member's scoregetScore
ZRANK / ZREVRANKGet a member's rankrank / revRank
ZINCRBYIncrement a member's scoreaddScore
ZCARDCount all memberssize
ZCOUNTCount members in a score rangecount
ZPOPMIN / ZPOPMAXRemove and return the lowest / highest memberpollFirst / pollLast
ZUNIONSTORE / ZINTERSTORECombine sorted setsunion / intersection

Putting It Together: A Sliding-Window Rate Limiter

These four commands combine neatly into a sliding-window rate limiter. Each request is recorded with its timestamp as the score (ZADD); expired requests are swept out (ZREMRANGEBYSCORE); and the remaining count (ZCARD) tells you whether the caller is over the limit:

long now = System.currentTimeMillis();
long windowMs = 60_000;   // a rolling one-minute window
RScoredSortedSet<String> hits = redisson.getScoredSortedSet("ratelimit:user:42");

// ZADD: record this request, scored by its timestamp
// (a unique member per request, so simultaneous calls never collide)
hits.add(now, now + ":" + UUID.randomUUID());

// ZREMRANGEBYSCORE: evict everything older than the window
hits.removeRangeByScore(0, true, now - windowMs, false);

// ZCARD: how many requests remain inside the window?
if (hits.size() > 100) {
    // reject: more than 100 requests in the last minute
}

// Keep the key from lingering after the user goes idle
hits.expire(Duration.ofMinutes(2));

For a full walkthrough of the leaderboard use case, see How to Build a Real-Time Leaderboard in Java with Valkey or Redis, and for a production-grade limiter, see Distributed Rate Limiting in Java With Valkey or Redis and Spring Boot.

Why Use Redisson Instead of Raw Commands?

You could send these commands yourself with a lower-level client, but Redisson's RScoredSortedSet adds several things Java developers care about:

  • Type safety: Members are real Java objects, not hand-encoded strings. Redisson's codecs handle serialization, so you can store domain objects directly as members.
  • Atomic operations beyond the base API: Methods such as addScoreAndGetRevRank are implemented with server-side Lua scripts, giving you write-and-read results in one atomic round trip.
  • Non-blocking APIs: Every method has asynchronous, Reactive, and RxJava counterparts for reactive and high-throughput services.
  • Scale: In a cluster, Redisson PRO can spread a single large sorted set across multiple master nodes via RClusteredScoredSortedSet, and performs read/write operations up to 4x faster.

To start building ranked, real-time features in Java on Valkey or Redis, try Redisson for free or compare the Redisson editions.

Similar Articles