How to Build a Real-Time Leaderboard in Java with Valkey or Redis
In the world of applications, leaderboards used to be something you typically saw in multiplayer online games and little else. But after businesses of all types and sizes underwent digital transformation, data analytics became their secret weapon. One of the most popular and fun ways to present the many new metrics these organizations have is with a leaderboard.
Whether it's showing which salespeople have generated the most revenue or which department came closest to meeting its goals this quarter, leaderboards tap into the competitive spirit of human nature. But presenting a list of high scores in a game is one thing. Implementing leaderboards in large-scale, distributed applications is something else altogether. As you build enterprise apps with leaderboards, the challenges of keeping them updated in real time quickly become apparent, thanks to the sheer scale of these apps and the limitations of traditional relational database platforms.
However, Java developers have a simple way to deliver real-time leaderboards, backed by either Valkey or Redis. That tool is Redisson, the Valkey/Redis client for Java developers, and its RScoredSortedSet object.
Why Sorted Sets Outperform Relational Databases for Leaderboards
The Redisson RScoredSortedSet interface is a distributed Java object, backed by the sorted sets functionality found in Valkey or Redis. But you may ask, "Why sorted sets? Can't I just use a SQL database for my leaderboard?"
It's true that, for small use cases, a relational database could make storing a leaderboard fairly simple. You may not even need to exploit the relational capabilities of the RDBMS and store everything in a flat table. You could run a basic query such as SELECT username, score FROM players ORDER BY score DESC LIMIT 10 to instantly retrieve your top 10 data. The problem is that when you try to deploy this approach at scale, the relational database quickly becomes a performance bottleneck.
The Struggle to Maintain Rank
To rank a single player, the database must evaluate the scores across the entire table, counting every row with a higher score. Every time a player earns a point, the underlying index on which the above query depends must be updated or rebuilt.
As your user base grows into the thousands or millions, the database will use all its resources in a struggle to keep up with users refreshing the leaderboard to check their rank while maintaining the write operations that update scores.
Sorted Sets, Instantaneous Updates
A sorted set, as implemented in Valkey and Redis, is a unique data structure that stores unique members (like a player ID) paired with a floating-point number (their score) while keeping these members perfectly ordered at all times.
Because the data remains perpetually sorted, write operations are incredibly efficient. More importantly, Valkey or Redis can return a specific user's exact rank instantly, without any scanning or counting — no table locks, no index rebuilding, and no system-taxing queries, as with a relational database.
Connecting Your Java App to Valkey or Redis
Now let's take a look at how you can quickly build a leaderboard in your Java app, backed by sorted sets in Valkey or Redis. Below, and throughout the rest of this article, we'll show you how to do everything with example Java code.
The first step is configuring Redisson to connect your Java app to either Valkey or Redis. You simply instantiate a configuration object and point it to your Valkey or Redis data store. For a complete walkthrough of connection options, see how to connect to Redis in Java. Finally, you initialize the client:
Config config = new Config();
// The connection URL remains the same whether you are using Redis or Valkey
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
RScoredSortedSet board = redisson.getScoredSortedSet("game:leaderboard");
Adding and Updating Leaderboard Scores in Real Time
Redisson's RScoredSortedSet interface offers three distinct options for updating scores, giving you the flexibility to code different add or update logic based on your app's needs:
// 1. Set or overwrite a player's absolute score
board.add(4200, "player:alice");
// 2. Atomically add points to an existing score; returns the new total
Double total = board.addScore("player:bob", 150);
// 3. Add to a score and retrieve the player's new rank in one round trip (0 = top)
Integer rank = board.addScoreAndGetRevRank("player:carol", 300);
The addScore method is powerful because it's completely atomic. If fifty different game servers attempt to award points to "player:bob" at the exact same millisecond, the engine will process each increment sequentially. No points are ever lost to race conditions.
The addScoreAndGetRevRank method combines a write and a read operation into a single network round trip. You can use this mechanism to send a user a celebratory message like "Congratulations! You just moved up to rank #7!" the moment it happens.
Displaying the Top Players and Ranks
Read operations are where sorted sets truly shine. Because leaderboards generally display the highest scores at the top, we utilize the "reverse-ordered" querying methods. In these methods, the highest score holds index 0.
// Fetch the Top 10 players, highest scores first, including the actual scores
Collection<ScoredEntry<String>> top10 = board.entryRangeReversed(0, 9);
for (ScoredEntry e : top10) {
System.out.println(e.getValue() + " -> " + e.getScore());
}
// Find one specific player's position (0-based; add 1 for a human-friendly UI rank)
Integer position = board.revRank("player:alice");
// Fetch Page 3 of the leaderboard: players ranked 100–149
Collection page = board.valueRangeReversed(100, 149);
// Calculate how many players sit in a specific score band (e.g., the Gold Tier)
int inBand = board.count(1000, true, 5000, true);
Use the entryRangeReversed method when you need both the player ID and their score to render the view. Conversely, valueRangeReversed returns only the members, which saves bandwidth when the scores themselves are not needed for that specific UI component.
Time-Windowed Leaderboards: Daily, Weekly, and All-Time Leaders
Over time, users can lose interest in a single leaderboard, even one that's updated in real time, especially if the same users keep appearing on the list. To maintain engagement, you can also offer time-windowed rankings, including daily, weekly, and all-time leaderboards.
In an RDBMS, you would rely on a combination of timestamps and heavy filtering to both build time-windowed leaderboards and keep them updated. But with sorted sets, you can apply TTL (time-to-live) values to distinct sorted sets per time window, keyed by the date, so that boards delete themselves as they become outdated. Here's how:
LocalDate today = LocalDate.now();
String dayKey = "leaderboard:daily:" + today; // leaderboard:daily:2026-06-09
String weekKey = String.format("leaderboard:weekly:%d-W%02d",
today.get(IsoFields.WEEK_BASED_YEAR),
today.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR)); // leaderboard:weekly:2026-W24
RScoredSortedSet daily = redisson.getScoredSortedSet(dayKey);
daily.addScore("player:alice", 150);
daily.expire(Duration.ofDays(2)); // Auto-delete two days later to save memory
Bringing the Advanced Features of Valkey or Redis to Java Developers
Valkey and Redis routinely receive praise for their lightning-fast performance. While it's true they often outperform relational databases, their real value comes from how they combine that speed with advanced features like sorted sets.
As the requirements for modern enterprise apps have become more demanding, the limitations of your old SQL query will quickly become apparent. But with Redisson, Java developers can easily take advantage of all Valkey and Redis have to offer. Beyond sorted sets, explore the top Redis-based Java objects to see how Redisson simplifies Maps, Sets, Queues, and more.