Skip to content

Best Practices

Follow these best practices to use Redlock4j effectively and safely in production.

All examples assume a RedlockManager manager created from a RedlockConfiguration, and locks obtained via manager.createLock(...).

Lock Management

Always Release Locks

Always release locks in a finally block:

Lock lock = manager.createLock("resource");
lock.lock();
try {
    // Critical section
} finally {
    lock.unlock();  // Always execute
}

Check Lock Acquisition

When you don't want to block indefinitely, use tryLock() and check the result:

Lock lock = manager.createLock("resource");
if (lock.tryLock()) {
    try {
        // Lock acquired successfully
    } finally {
        lock.unlock();
    }
} else {
    // Failed to acquire lock - handle appropriately
    handleLockFailure();
}

Use Appropriate TTL

Set the lock timeout longer than your operation. The lock's time-to-live is the defaultLockTimeout configured on the manager:

// Bad: TTL too short
RedlockConfiguration bad = RedlockConfiguration.builder()
    .addRedisNode("redis1", 6379)
    .defaultLockTimeout(Duration.ofSeconds(1))  // 1 second
    .build();
// performLongOperation() takes 5 seconds - lock will expire!

// Good: TTL with safety margin
RedlockConfiguration good = RedlockConfiguration.builder()
    .addRedisNode("redis1", 6379)
    .defaultLockTimeout(Duration.ofSeconds(10))  // 10 seconds
    .build();
// performLongOperation() takes 5 seconds - safe

If an operation occasionally runs long, extend the lock instead of setting an excessively large TTL: ((Redlock) lock).extend(10000).

Redis Configuration

Use Independent Redis Instances

For production, use truly independent Redis instances:

// Good: Independent instances on different servers
RedlockConfiguration config = RedlockConfiguration.builder()
    .addRedisNode("redis1.example.com", 6379)
    .addRedisNode("redis2.example.com", 6379)
    .addRedisNode("redis3.example.com", 6379)
    .build();

// Bad: Master-slave replication (not independent)
// Don't use a master and its slaves as separate nodes

Minimum 3 Instances

Use at least 3 Redis instances for fault tolerance:

// Minimum for fault tolerance
RedlockConfiguration config = RedlockConfiguration.builder()
    .addRedisNode("redis1", 6379)
    .addRedisNode("redis2", 6379)
    .addRedisNode("redis3", 6379)
    .build();

// Better: 5 instances for higher availability
RedlockConfiguration config = RedlockConfiguration.builder()
    .addRedisNode("redis1", 6379)
    .addRedisNode("redis2", 6379)
    .addRedisNode("redis3", 6379)
    .addRedisNode("redis4", 6379)
    .addRedisNode("redis5", 6379)
    .build();

A single node is also valid for local development or non-critical use: with one node Redlock4j runs in single-node mode. With three or more nodes it requires a quorum.

Use Odd Numbers

Always use an odd number of instances:

  • 3, 5, 7 instances
  • 2, 4, 6 instances

Exactly 2 nodes is not supported

A configuration with exactly 2 nodes throws IllegalArgumentException at build(). Use 1 node (single-node mode) or 3 or more nodes (quorum mode). Two nodes cannot form a meaningful quorum, so it is rejected outright.

Error Handling

Handle Lock Failures

Lock lock = manager.createLock("resource");
if (!lock.tryLock()) {
    // Log the failure
    logger.warn("Failed to acquire lock for resource");

    // Implement a fallback strategy
    // Option 1: Retry later
    scheduleRetry();

    // Option 2: Return an error to the caller
    throw new LockAcquisitionException("Could not acquire lock");

    // Option 3: Use an alternative approach
    performAlternativeOperation();
}

Handle Exceptions

Lock lock = manager.createLock("resource");
boolean acquired = false;
try {
    acquired = lock.tryLock();
    if (acquired) {
        performCriticalOperation();
    }
} catch (Exception e) {
    logger.error("Error in critical section", e);
    handleError(e);
} finally {
    if (acquired) {
        try {
            lock.unlock();
        } catch (Exception e) {
            logger.error("Error releasing lock", e);
            // Don't throw - we're in a finally block
        }
    }
}

Performance

Reuse the RedlockManager

Create the RedlockManager once and reuse it. It owns the Redis connections, so creating one per operation is wasteful. Creating individual locks from a shared manager is cheap:

// Good: single shared manager
public class LockService {
    private static final RedlockManager MANAGER = RedlockManager.withJedis(createConfig());

    public Lock acquireLock(String resource) {
        Lock lock = MANAGER.createLock(resource);
        lock.lock();
        return lock;
    }
}

// Bad: creating a new manager each time
public Lock acquireLock(String resource) {
    RedlockManager manager = RedlockManager.withJedis(createConfig());  // Wasteful!
    Lock lock = manager.createLock(resource);
    lock.lock();
    return lock;
}

Tune Node Connections

Connection tuning happens per node via RedisNodeConfiguration, not via a pool config object:

RedisNodeConfiguration node = RedisNodeConfiguration.builder()
    .host("redis1")
    .port(6379)
    .connectionTimeoutMs(2000)  // time to establish a connection
    .socketTimeoutMs(2000)      // time to wait on a command
    .build();

Use Appropriate Retry Settings

RedlockConfiguration config = RedlockConfiguration.builder()
    .addRedisNode("redis1", 6379)
    .addRedisNode("redis2", 6379)
    .addRedisNode("redis3", 6379)
    .maxRetryAttempts(3)                  // Don't retry too many times
    .retryDelay(Duration.ofMillis(200))   // Reasonable delay between retries
    .build();

Resource Naming

Use Descriptive Names

// Good: clear and descriptive
Lock lock = manager.createLock("user:123:profile:update");
Lock lock = manager.createLock("order:456:payment:process");

// Bad: unclear names
Lock lock = manager.createLock("lock1");
Lock lock = manager.createLock("temp");

Use a Consistent Naming Convention

// Establish a pattern
String lockKey = String.format("%s:%s:%s",
    entityType,    // "user", "order", "product"
    entityId,      // "123", "456"
    operation);    // "update", "delete", "process"

Lock lock = manager.createLock(lockKey);

Monitoring and Logging

Log Lock Operations

Lock lock = manager.createLock(resourceId);
if (lock.tryLock()) {
    logger.info("Acquired lock for resource: {}", resourceId);
    try {
        performOperation();
    } finally {
        lock.unlock();
        logger.info("Released lock for resource: {}", resourceId);
    }
} else {
    logger.warn("Failed to acquire lock for resource: {}", resourceId);
}

Monitor Lock Metrics

Track important metrics:

  • Lock acquisition success rate
  • Lock acquisition latency
  • Lock hold time
  • Lock contention rate

Testing

Test Lock Behavior

@Test
public void testLockAcquisition() {
    Lock lock = manager.createLock("test-resource");
    assertTrue(lock.tryLock(), "Should acquire lock");

    // Try to acquire the same lock from another thread - should fail
    Lock lock2 = manager.createLock("test-resource");
    assertFalse(lockFromOtherThread(lock2), "Should not acquire an already locked resource");

    lock.unlock();
}

Use Testcontainers

For integration tests, point node configuration at the container's mapped port:

@Testcontainers
public class RedlockIntegrationTest {
    @Container
    private static GenericContainer<?> redis =
        new GenericContainer<>("redis:7-alpine")
            .withExposedPorts(6379);

    @Test
    public void testWithRealRedis() {
        RedlockConfiguration config = RedlockConfiguration.builder()
            .addRedisNode(redis.getHost(), redis.getFirstMappedPort())
            .build();

        try (RedlockManager manager = RedlockManager.withJedis(config)) {
            // Test with real Redis
        }
    }
}

Common Pitfalls

Don't Forget to Unlock

// Bad: no unlock
Lock lock = manager.createLock("resource");
lock.lock();
performOperation();  // If this throws, the lock is never released!

Always pair lock() / successful tryLock() with unlock() in a finally block.

Don't Configure Exactly 2 Nodes

// Bad: exactly 2 nodes throws IllegalArgumentException at build()
RedlockConfiguration config = RedlockConfiguration.builder()
    .addRedisNode("redis1", 6379)
    .addRedisNode("redis2", 6379)
    .build();  // throws!

Use 1 node (single-node mode) or 3+ nodes (quorum mode).

Don't Ignore Lock Acquisition Failures

// Bad: assuming the lock is always acquired
Lock lock = manager.createLock("resource");
lock.tryLock();          // return value ignored
performOperation();      // What if the lock wasn't acquired?

Next Steps