Redis Lua Scripting in Spring Boot: Atomic Operations with Spring Data Redis
Redis Lua scripting lets you execute multiple Redis commands as one atomic operation. In a Spring Boot application, this is useful when a read, a condition, and a write must happen together—for example, limiting requests, updating inventory, or incrementing a counter only while it remains below a limit.
In this guide, you’ll learn how to run a Redis Lua script from Spring Boot using StringRedisTemplate and Spring Data Redis. The example implements an atomic counter with a maximum value.
What Is Redis Lua Scripting?
Redis can execute Lua scripts on the server. A script can read and update Redis data without another client’s commands interleaving during its execution. This makes scripts useful for small operations that require atomic behavior.
Redis scripts commonly use:
KEYSfor Redis keys passed to the script.ARGVfor additional arguments passed to the script.
For example, KEYS[1] refers to the first key supplied by the application, and ARGV[1] refers to the first non-key argument.
When Should You Use a Lua Script?
Consider a Lua script when an operation must check a value and update it as one atomic unit. Common examples include:
- Incrementing a counter only if it is below a limit.
- Decrementing inventory only when stock is available.
- Implementing a simple rate limiter.
- Updating related Redis values together.
For a single Redis command, such as a straightforward INCR, a Lua script is usually unnecessary. Keep scripts short because Redis executes them on the server and other commands wait while a script runs.
Example: Increment a Counter Up to a Limit
The following script reads a counter, treats a missing key as zero, and increments it only when it is below the supplied limit. It returns the new counter value after an increment, or -1 if the limit has been reached.
local current = tonumber(redis.call('GET', KEYS[1]) or '0')
local limit = tonumber(ARGV[1])
if current < limit then
return redis.call('INCR', KEYS[1])
end
return -1
Because the read, comparison, and increment happen inside one script, two clients cannot both read the same old value and exceed the limit through competing updates.
Configure Spring Data Redis
Add Spring Data Redis to your Spring Boot project. With Maven, the dependency is typically:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Configure your Redis connection using your project’s Spring Boot configuration. For example, in application.properties:
spring.data.redis.host=localhost
spring.data.redis.port=6379
Use the configuration property names appropriate to your Spring Boot version and deployment environment. For production, keep credentials and connection details in secure configuration rather than committing secrets to source control.
Create a Spring Service to Execute the Script
Spring Data Redis provides DefaultRedisScript for defining a script and its expected result type. The service below uses constructor injection and creates the script once rather than rebuilding it for every call.
package com.example.redis;
import java.util.Collections;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
@Service
public class LimitedCounterService {
private static final String INCREMENT_IF_BELOW_LIMIT = """
local current = tonumber(redis.call('GET', KEYS[1]) or '0')
local limit = tonumber(ARGV[1])
if current < limit then
return redis.call('INCR', KEYS[1])
end
return -1
""";
private static final DefaultRedisScript<Long> SCRIPT =
new DefaultRedisScript<>(
INCREMENT_IF_BELOW_LIMIT,
Long.class
);
private final StringRedisTemplate redisTemplate;
public LimitedCounterService(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* Increments the Redis counter while it is below the given limit.
*
* @param key Redis key for the counter
* @param limit maximum counter value
* @return the incremented value, or -1 if the limit has been reached
*/
public long incrementIfBelowLimit(String key, long limit) {
Long result = redisTemplate.execute(
SCRIPT,
Collections.singletonList(key),
Long.toString(limit)
);
if (result == null) {
throw new IllegalStateException(
"Redis Lua script returned no result"
);
}
return result;
}
}
Call the Service
Inject LimitedCounterService into your application component and call the method with a Redis key and its limit:
long result = limitedCounterService.incrementIfBelowLimit(
"requests:user:42",
100
);
if (result == -1) {
// The counter has reached its limit.
} else {
// The counter was incremented; result is the new value.
}
In this example, the key requests:user:42 is passed as KEYS[1], while the limit is passed as ARGV[1].
Important Considerations
- Choose a clear return value: This example uses
-1to indicate that the limit has been reached. Choose a different result convention if negative values could be valid in your application. - Validate your data: The script assumes the counter contains a numeric value and the limit can be converted to a Lua number. Define how your application should handle invalid stored data.
- Keep scripts fast: A Lua script blocks other Redis commands while it runs. Avoid long loops and expensive work.
- Pass keys explicitly: Pass Redis keys through the script’s key list instead of embedding key names in the script. This is especially important when using Redis Cluster.
- Set an expiration when needed: If the counter is temporary—for example, part of a time-based rate limit—add an expiration strategy suited to your use case.
- Test against Redis: Integration-test scripts with the Redis version and configuration used by your application.
Redis Lua Scripting FAQ
Does a Redis Lua script run atomically?
Redis executes a script without interleaving other client commands during the script’s execution. This is useful for combining a read, a condition, and an update into one operation.
What is the difference between KEYS and ARGV?
KEYS contains the Redis keys supplied to the script. ARGV contains other input values, such as a counter limit or expiration duration.
Can I use Lua scripts with Spring Boot?
Yes. Spring Data Redis can execute scripts using RedisScript or DefaultRedisScript with a Redis template such as StringRedisTemplate.
Should every Redis operation use Lua?
No. Use normal Redis commands for operations that can be safely performed with a single command. Lua scripting is most useful when multiple operations must be treated as one atomic operation.
Conclusion
Redis Lua scripting provides a practical way to perform small, atomic operations on the Redis server. With Spring Data Redis, you can define a script using DefaultRedisScript and execute it through StringRedisTemplate. The counter example demonstrates how to check a limit and conditionally increment a value without a race between separate Redis commands.
Use scripts selectively, keep them short, and make their inputs and return values explicit. These practices help make Redis scripting easier to maintain in a Spring Boot application.