Distributed Locking with Redis and Spring Boot: Implementation Guide

    Distributed Locking with Redis and Spring Boot: Implementation Guide

    28/01/2026

    Introduction

    In modern microservices architectures, we often run multiple instances of our applications to ensure high availability and scalability. While this is great for performance, it introduces a new set of challenges when it comes to managing shared resources.

    One of the most common challenges is ensuring that a specific critical section of code is executed by only one instance at a time. This is where distributed locking comes into play.

    The problem: The double booking nightmare

    Imagine you are building a movie ticket booking application. You have a TicketService with a method bookSeat(String seatId, String userId).

    If you have a single instance of your application, you might use Java's synchronized keyword or a ReentrantLock to ensure that two users don't book the same seat simultaneously.

    // Works fine for a Single Instance public synchronized void bookSeat(String seatId, String userId) { if (isSeatAvailable(seatId)) { reserverSeat(seatId, userId); } }

    However, in a distributed environment with multiple instances of the service running (e.g., on Kubernetes), synchronized only works within a single JVM.

    Service Instance A User A request Local Lock OK Service Instance B User B request Local Lock OK Database Double Booking!

    Since Instance A and Instance B don't share memory, their local locks are unaware of each other. User A and User B could both check availability at the exact same moment, see the seat as "Available", and both proceed to book it. Result: Double Booking.

    The solution: Distributed locking with Redis

    To solve this, we need a lock that is external to our application instances—a shared source of truth. Redis is an excellent choice for this. It processes commands sequentially (single-threaded), ensuring atomic operations. It is extremely fast because it runs in-memory, and it supports key expiration, which is crucial for preventing deadlocks.

    How it works

    The algorithm requires acquiring a lock by trying to set a locking key in Redis (e.g., lock:seat:1A). If the key sets successfully, you have the lock and can proceed. If the key already exists, someone else has the lock, so you must wait or fail. When you finish, you release the lock by deleting the key.

    Key concepts

    Two core Redis features make this work. First, the SETNX (SET if Not eXists) command. This operation only sets the key if it does not already exist, which is exactly the behavior we need to acquire a lock atomically.

    Second, we need expiration (TTL). What if the service crashes after acquiring the lock but before releasing it? The lock would stay forever, creating a deadlock. To prevent this, we set a Time-To-Live (TTL) on the lock key so the database automatically releases it if the application dies.

    Implementation in Spring Boot

    Here is how we implement a robust distributed lock using RedisTemplate.

    1. Dependencies

    Add the Redis starter to your pom.xml:

    <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>

    2. RedisLock utility

    We'll create a component to handle the locking logic.

    import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import java.time.Duration; import java.util.concurrent.TimeUnit; @Component public class RedisLock { private final StringRedisTemplate redisTemplate; public RedisLock(StringRedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; } /** * Tries to acquire the lock. * @param key The unique key for the resource (e.g., "lock:seat:123") * @param timeout The duration for which the lock should be held (TTL) * @return true if lock is acquired, false otherwise */ public boolean acquireLock(String key, Duration timeout) { // SET key value NX EX timeout Boolean success = redisTemplate.opsForValue() .setIfAbsent(key, "LOCKED", timeout); return Boolean.TRUE.equals(success); } /** * Releases the lock by deleting the key. * @param key The unique key to release */ public void releaseLock(String key) { redisTemplate.delete(key); } }

    Note: setIfAbsent corresponds to the atomic SET ... NX EX ... command in Redis. It ensures we set the key and the expiry in one atomic operation.

    3. Ticket booking service

    Now, let's use this lock in our TicketService.

    import org.springframework.stereotype.Service; import java.time.Duration; @Service public class TicketService { private final RedisLock redisLock; private final BookingRepository bookingRepository; public TicketService(RedisLock redisLock, BookingRepository bookingRepository) { this.redisLock = redisLock; this.bookingRepository = bookingRepository; } public void bookSeat(String seatId, String userId) { String lockKey = "lock:seat:" + seatId; // Define how long we hold the lock. // Should be longer than the expected execution time. Duration lockTimeout = Duration.ofSeconds(10); try { // 1. Try to acquire the lock if (redisLock.acquireLock(lockKey, lockTimeout)) { // 2. Critical Section // Double check availability in DB just to be sure if (isSeatAvailable(seatId)) { // Simulate processing (e.g., payment) processPayment(); reserveSeatInDb(seatId, userId); System.out.println("Seat " + seatId + " booked for " + userId); } else { throw new RuntimeException("Seat already booked!"); } } else { // 3. Failed to acquire lock throw new RuntimeException("Seat is currently being booked by someone else. Please try again."); } } finally { // 4. Always release the lock! redisLock.releaseLock(lockKey); } } private boolean isSeatAvailable(String seatId) { // Logic to check DB return true; } private void reserveSeatInDb(String seatId, String userId) { // Logic to save booking } private void processPayment() { try { Thread.sleep(2000); // Simulate network call } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }

    Improving the solution

    The implementation above functions well, but production systems have edge cases that quickly complicate things.

    Consider accidental releases. What if Instance A acquires a lock but takes too long processing, causing the lock to expire? Instance B then acquires the lock. Finally, Instance A finishes its work and deletes the key. Instance A just deleted Instance B's lock! The fix here is to store a unique UUID as the value of the lock key. When releasing, you check if the value matches your UUID before deleting. This requires a Lua script to remain atomic.

    Then there is Redis failover. If the Redis master crashes after a lock is acquired but before it replicates to the replica, consistency goes out the window. You can mitigate this using the Redlock algorithm or by ensuring strong consistency in your Redis cluster, but it adds significant operational overhead.

    Production recommendation: Redisson

    For production applications, don't reinvent the wheel. Use Redisson, a popular Redis client for Java that implements java.util.concurrent.locks.Lock backed by Redis. It handles:

    • Automatic lease extension (Watchdog).
    • Correct atomic release logic.
    • Wait strategies (spin locks, pub/sub for release events).

    Here is how simple it is with Redisson:

    RLock lock = redissonClient.getLock("lock:seat:" + seatId); try { if (lock.tryLock(1, 10, TimeUnit.SECONDS)) { // Critical section } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } }

    Conclusion

    Distributed locking ensures data consistency in microservices, and Redis provides a fast, reliable foundation for implementing these locks using atomic operations and key expirations. However, building a custom lock requires careful handling of edge cases like accidental releases and failovers. Rather than writing this logic from scratch, use libraries like Redisson that handle lease management and failover automatically. By managing your critical sections correctly, you avoid double booking nightmares down the road.

    🔗 Blog 🔗 LinkedIn 🔗 Medium 🔗 Github

    Discover Top YouTube Creators

    Explore Popular Tech YouTube Channels

    Find the most popular YouTube creators in tech categories like AI, Java, JavaScript, Python, .NET, and developer conferences. Perfect for learning, inspiration, and staying updated with the best tech content.

    Summarise

    Transform Your Learning

    Get instant AI-powered summaries of YouTube videos and websites. Save time while enhancing your learning experience.

    Instant video summaries
    Smart insights extraction
    Channel tracking