Transactions/Multi
Overview
Transactions guarantee that all the included commands will execute to completion without being interrupted by commands from other clients. See the Transactions page for more information.
To execute commands in a transaction, create a transaction object with the multi() command, call command methods on that object, and then call the transaction object's exec() method to execute it. You can access the results from commands in the transaction using Response objects. The exec() method also returns a List<Object> value that contains all the result values in the order the commands were executed.
Immediate Transaction Start
The simplest way to use transactions is to call multi() on your Jedis client, which immediately starts a transaction by sending the MULTI command to Redis. All subsequent commands are queued until exec() is called.
import redis.clients.jedis.RedisClient;
import redis.clients.jedis.AbstractTransaction;
import redis.clients.jedis.Response;
import java.util.List;
RedisClient jedis = RedisClient.create("redis://localhost:6379");
// Create a transaction that immediately sends MULTI
try (AbstractTransaction tx = jedis.multi()) {
// Commands are queued
Response<String> set1 = tx.set("counter:1", "0");
Response<Long> incr1 = tx.incrBy("counter:1", 1);
Response<Long> incr2 = tx.incrBy("counter:1", 2);
// Execute the transaction
List<Object> results = tx.exec();
// Access results via Response objects
System.out.println(incr1.get()); // 1
System.out.println(incr2.get()); // 3
// Or via the results list
System.out.println(results.get(0)); // OK
System.out.println(results.get(1)); // 1
System.out.println(results.get(2)); // 3
}
jedis.close();
Response Handling
Commands invoked within a transaction return Response<T> objects. These responses become available only after exec() is called:
- Before
exec(): Callingresponse.get()will throwIllegalStateExceptionwith the message "Please close pipeline or multi block before calling this method." - After
exec(): Response objects contain the actual results from Redis
The exec() method returns a List<Object> containing all command results in the order they were queued.
Manual Transaction Start
For more control, you can create a transaction without immediately sending MULTI. This is useful when you need to:
- Execute commands before starting the transaction
- Use
WATCHto implement optimistic locking - Conditionally start the transaction
Create a manual transaction by passing false to the transaction() method:
RedisClient jedis = RedisClient.create("redis://localhost:6379");
// Create transaction without sending MULTI
try (AbstractTransaction tx = jedis.transaction(false)) {
// Commands before multi() are executed immediately
Response<String> setBeforeMulti = tx.set("mykey", "initial_value");
Response<String> getBeforeMulti = tx.get("mykey");
// These responses are available immediately
System.out.println(setBeforeMulti.get()); // OK
System.out.println(getBeforeMulti.get()); // initial_value
// Now start the transaction
tx.multi();
// Commands after multi() are queued
Response<String> set = tx.set("mykey", "new_value");
Response<String> get = tx.get("mykey");
// Execute the transaction
List<Object> results = tx.exec();
// Results from queued commands
System.out.println(set.get()); // OK
System.out.println(get.get()); // new_value
}
jedis.close();
Using WATCH for Optimistic Locking
The WATCH command monitors keys for changes. If any watched key is modified before EXEC, the transaction is aborted and exec() returns null.
Important: WATCH must be executed through the transaction object to ensure it uses the transaction's dedicated connection.
try (AbstractTransaction tx = jedis.transaction(false)) {
// WATCH must be on the transaction's dedicated connection
tx.watch("counter");
// Read current value - can use the client directly
String current = jedis.get("counter");
int newValue = Integer.parseInt(current) + 1;
// Start transaction and queue update
tx.multi();
tx.set("counter", String.valueOf(newValue));
// Returns null if key was modified by another client
List<Object> results = tx.exec();
if (results == null) {
System.out.println("Transaction aborted - key was modified");
}
}
Connection Lifecycle
A transaction acquires a dedicated connection that is held until close() is called. Ensure the transaction is closed on error:
Important: WATCH must be executed through the transaction object to ensure it uses the transaction's dedicated connection.
Transaction Completion
Complete a transaction by calling either:
exec()- Executes all queued commands atomically and returns aList<Object>with the resultsdiscard()- Discards all queued commands without executing them
Automatic Cleanup
When using try-with-resources, close() automatically sends DISCARD (if in MULTI state) or UNWATCH (if in WATCH state) to ensure the connection is returned to the pool in a clean state.