Skip to content

Annotation Reference

Garvit Joshi edited this page Mar 6, 2026 · 12 revisions

Annotation Reference

Complete reference for @DistributedLock, @DistributedSemaphore, @RateLimit, and related types.


@DistributedLock

The main annotation for distributed locking.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributedLock {
    String key();
    LockType type() default LockType.REENTRANT;
    AcquisitionMode mode() default AcquisitionMode.SKIP_IMMEDIATELY;
    String leaseTime() default "";
    String waitTime() default "";
    boolean autoRenew() default false;
    Class<? extends LockSkipHandler> skipHandler() default LockThrowExceptionHandler.class;
    LeaseExpirationBehavior onLeaseExpired() default LeaseExpirationBehavior.LOG_WARNING;
}

Lock Attributes

key (required)

The lock identifier. Supports SpEL expressions.

// Static key
@DistributedLock(key = "my-lock")

// Dynamic key with SpEL
@DistributedLock(key = "#{#userId}")
@DistributedLock(key = "#{'order-' + #orderId}")
@DistributedLock(key = "#{#user.id}")

The final Redis key is: {locksmith.lock.key-prefix}{key}

Config Prefix Annotation Key Redis Key
lock: task lock:task
myapp: user-123 myapp:user-123
lock: #{#orderId} (="ABC") lock:ABC

See: Dynamic Keys with SpEL


type

The type of lock to acquire.

Value Description
REENTRANT Exclusive lock, same thread can re-enter (default)
READ Shared lock, multiple readers allowed
WRITE Exclusive lock, blocks readers and writers
@DistributedLock(key = "resource", type = LockType.REENTRANT)
@DistributedLock(key = "resource", type = LockType.READ)
@DistributedLock(key = "resource", type = LockType.WRITE)

See: Lock Types


mode

How to handle lock acquisition.

Value Description
SKIP_IMMEDIATELY Fail instantly if lock is held (default)
WAIT_AND_SKIP Wait up to waitTime before failing
// Fail immediately
@DistributedLock(key = "task", mode = AcquisitionMode.SKIP_IMMEDIATELY)

// Wait then fail
@DistributedLock(
    key = "task",
    mode = AcquisitionMode.WAIT_AND_SKIP,
    waitTime = "30s"
)

See: Lock Acquisition Modes


leaseTime

How long the lock is held before auto-release.

Value Meaning
"" (empty) Use global config (default)
Duration string Override for this method
@DistributedLock(key = "task")                    // Uses config default
@DistributedLock(key = "task", leaseTime = "5m")  // 5 minutes
@DistributedLock(key = "task", leaseTime = "30s") // 30 seconds
@DistributedLock(key = "task", leaseTime = "PT1H") // 1 hour (ISO-8601)

Duration formats:

  • Simple: 500ms, 30s, 5m, 1h
  • ISO-8601: PT30S, PT5M, PT1H

Note: Ignored when autoRenew = true


waitTime

How long to wait in WAIT_AND_SKIP mode.

Value Meaning
"" (empty) Use global config (default)
Duration string Override for this method
@DistributedLock(
    key = "task",
    mode = AcquisitionMode.WAIT_AND_SKIP,
    waitTime = "30s"
)

Only effective when mode = WAIT_AND_SKIP.


autoRenew

Enable automatic lease renewal via Redisson's watchdog.

Value Description
false Use fixed lease time (default)
true Auto-extend lock during execution
@DistributedLock(key = "long-task", autoRenew = true)

When enabled:

  • leaseTime is ignored
  • onLeaseExpired has no effect
  • Lock is held until method completes

See: Auto-Renew Lease Time


skipHandler

Handler invoked when lock acquisition fails. Handlers are resolved as Spring beans first (supporting dependency injection), with reflection fallback.

Value Description
LockThrowExceptionHandler.class Throw LockNotAcquiredException (default)
LockReturnDefaultHandler.class Return null/0/false/Optional.empty()
Custom class Your implementation (can be a @Component)
@DistributedLock(key = "task")  // Throws exception
@DistributedLock(key = "task", skipHandler = LockReturnDefaultHandler.class)
@DistributedLock(key = "task", skipHandler = MyCustomHandler.class)  // Can be @Component with DI

See: Skip Handlers


onLeaseExpired

Behavior when execution exceeds lease time.

Value Description
LOG_WARNING Log a warning message (default)
THROW_EXCEPTION Throw LeaseExpiredException
IGNORE Silently ignore
@DistributedLock(key = "task", onLeaseExpired = LeaseExpirationBehavior.LOG_WARNING)
@DistributedLock(key = "task", onLeaseExpired = LeaseExpirationBehavior.THROW_EXCEPTION)
@DistributedLock(key = "task", onLeaseExpired = LeaseExpirationBehavior.IGNORE)

Note: Has no effect when autoRenew = true

See: Lease Expiration Detection


@DistributedSemaphore

Annotation for permit-based concurrency control.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributedSemaphore {
    String key();
    int permits() default 1;
    AcquisitionMode mode() default AcquisitionMode.SKIP_IMMEDIATELY;
    String leaseTime() default "";
    String waitTime() default "";
    Class<? extends SemaphoreSkipHandler> skipHandler() default SemaphoreThrowExceptionHandler.class;
    LeaseExpirationBehavior onLeaseExpired() default LeaseExpirationBehavior.LOG_WARNING;
}

Semaphore Attributes

key (required)

The semaphore identifier. Supports SpEL expressions.

// Static key
@DistributedSemaphore(key = "api-pool", permits = 5)

// Dynamic key with SpEL
@DistributedSemaphore(key = "#{#userId}", permits = 3)
@DistributedSemaphore(key = "#{'tenant-' + #tenantId}", permits = 10)

The final Redis key is: {locksmith.semaphore.key-prefix}{key}

See: Dynamic Keys with SpEL


permits (required)

Maximum number of concurrent executions allowed.

@DistributedSemaphore(key = "pool", permits = 5)   // 5 concurrent
@DistributedSemaphore(key = "pool", permits = 10)  // 10 concurrent
@DistributedSemaphore(key = "pool", permits = 1)   // 1 concurrent (like a lock)

Important: Must be a positive integer. When the same key is used across multiple methods, the permits value must be consistent.


mode

How to handle permit acquisition.

Value Description
SKIP_IMMEDIATELY Fail instantly if no permit available (default)
WAIT_AND_SKIP Wait up to waitTime before failing
// Fail immediately
@DistributedSemaphore(key = "pool", permits = 5, mode = AcquisitionMode.SKIP_IMMEDIATELY)

// Wait then fail
@DistributedSemaphore(
    key = "pool",
    permits = 5,
    mode = AcquisitionMode.WAIT_AND_SKIP,
    waitTime = "30s"
)

See: Lock Acquisition Modes


leaseTime

How long a permit is held before auto-release.

Value Meaning
"" (empty) Use global config (default)
Duration string Override for this method
@DistributedSemaphore(key = "pool", permits = 5)                    // Uses config default
@DistributedSemaphore(key = "pool", permits = 5, leaseTime = "5m")  // 5 minutes
@DistributedSemaphore(key = "pool", permits = 5, leaseTime = "30s") // 30 seconds

waitTime

How long to wait in WAIT_AND_SKIP mode.

Value Meaning
"" (empty) Use global config (default)
Duration string Override for this method
@DistributedSemaphore(
    key = "pool",
    permits = 5,
    mode = AcquisitionMode.WAIT_AND_SKIP,
    waitTime = "30s"
)

Only effective when mode = WAIT_AND_SKIP.


skipHandler

Handler invoked when permit acquisition fails. Handlers are resolved as Spring beans first (supporting dependency injection), with reflection fallback.

Value Description
SemaphoreThrowExceptionHandler.class Throw SemaphoreNotAcquiredException (default)
SemaphoreReturnDefaultHandler.class Return null/0/false/Optional.empty()
Custom class Your implementation (can be a @Component)
@DistributedSemaphore(key = "pool", permits = 5)  // Throws exception
@DistributedSemaphore(key = "pool", permits = 5, skipHandler = SemaphoreReturnDefaultHandler.class)
@DistributedSemaphore(key = "pool", permits = 5, skipHandler = MySemaphoreHandler.class)  // Can be @Component with DI

See: Skip Handlers


onLeaseExpired

Behavior when execution exceeds lease time.

Value Description
LOG_WARNING Log a warning message (default)
THROW_EXCEPTION Throw SemaphoreLeaseExpiredException
IGNORE Silently ignore
@DistributedSemaphore(key = "pool", permits = 5, onLeaseExpired = LeaseExpirationBehavior.LOG_WARNING)
@DistributedSemaphore(key = "pool", permits = 5, onLeaseExpired = LeaseExpirationBehavior.THROW_EXCEPTION)
@DistributedSemaphore(key = "pool", permits = 5, onLeaseExpired = LeaseExpirationBehavior.IGNORE)

See: Lease Expiration Detection


@RateLimit

Annotation for rate limiting - controlling request throughput over time.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
    String key();
    long permits() default 10;
    String interval() default "1s";
    RateType type() default RateType.OVERALL;
    AcquisitionMode mode() default AcquisitionMode.SKIP_IMMEDIATELY;
    String waitTime() default "";
    Class<? extends RateLimitSkipHandler> skipHandler() default RateLimitThrowExceptionHandler.class;
}

Rate Limit Attributes

key (required)

The rate limiter identifier. Supports SpEL expressions.

// Static key
@RateLimit(key = "api-endpoint", permits = 100, interval = "1m")

// Dynamic key with SpEL
@RateLimit(key = "#{#userId}", permits = 100, interval = "1m")
@RateLimit(key = "#{'api-' + #region}", permits = 50, interval = "1m")

The final Redis key is: {locksmith.rate-limit.key-prefix}{key}

See: Dynamic Keys with SpEL


permits

Number of permits allowed per interval.

Value Meaning
10 (default) 10 requests per interval
Any positive long Custom limit
@RateLimit(key = "api", permits = 100)   // 100 per interval
@RateLimit(key = "api", permits = 1000)  // 1000 per interval

interval

Time window for the permits.

Value Meaning
"1s" (default) 1 second
Duration string Custom interval
@RateLimit(key = "api", permits = 100, interval = "1s")   // 100/second
@RateLimit(key = "api", permits = 100, interval = "1m")   // 100/minute
@RateLimit(key = "api", permits = 1000, interval = "1h")  // 1000/hour
@RateLimit(key = "api", permits = 5, interval = "500ms")  // 5 per 500ms

Duration formats:

  • Simple: 500ms, 30s, 5m, 1h
  • ISO-8601: PT30S, PT5M, PT1H

type

The scope of rate limiting.

Value Description
OVERALL Permits shared across all clients/instances (default)
PER_CLIENT Each Redisson client instance gets its own quota
// Shared across all instances
@RateLimit(key = "api", permits = 100, interval = "1m", type = RateType.OVERALL)

// Each instance gets its own limit
@RateLimit(key = "api", permits = 100, interval = "1m", type = RateType.PER_CLIENT)

See: Rate Limiting


mode

How to handle permit acquisition.

Value Description
SKIP_IMMEDIATELY Fail instantly if rate limit exceeded (default)
WAIT_AND_SKIP Wait up to waitTime before failing
// Fail immediately
@RateLimit(key = "api", permits = 100, interval = "1m", mode = AcquisitionMode.SKIP_IMMEDIATELY)

// Wait then fail
@RateLimit(
    key = "api",
    permits = 100,
    interval = "1m",
    mode = AcquisitionMode.WAIT_AND_SKIP,
    waitTime = "5s"
)

See: Lock Acquisition Modes


waitTime

How long to wait in WAIT_AND_SKIP mode.

Value Meaning
"" (empty) Use global config (default)
Duration string Override for this method
@RateLimit(
    key = "api",
    permits = 100,
    interval = "1m",
    mode = AcquisitionMode.WAIT_AND_SKIP,
    waitTime = "5s"
)

Only effective when mode = WAIT_AND_SKIP.


skipHandler

Handler invoked when rate limit is exceeded. Handlers are resolved as Spring beans first (supporting dependency injection), with reflection fallback.

Value Description
RateLimitThrowExceptionHandler.class Throw RateLimitExceededException (default)
RateLimitReturnDefaultHandler.class Return null/0/false/Optional.empty()
Custom class Your implementation (can be a @Component)
@RateLimit(key = "api", permits = 100, interval = "1m")  // Throws exception
@RateLimit(key = "api", permits = 100, interval = "1m", skipHandler = RateLimitReturnDefaultHandler.class)
@RateLimit(key = "api", permits = 100, interval = "1m", skipHandler = MyCustomHandler.class)

See: Skip Handlers


Enums

LockType

public enum LockType {
    REENTRANT,  // Exclusive, reentrant lock
    READ,       // Shared read lock
    WRITE       // Exclusive write lock
}

AcquisitionMode

Used by @DistributedLock, @DistributedSemaphore, and @RateLimit.

public enum AcquisitionMode {
    SKIP_IMMEDIATELY,  // Fail instantly if locked/no permit
    WAIT_AND_SKIP      // Wait up to waitTime
}

LeaseExpirationBehavior

Used by both @DistributedLock and @DistributedSemaphore.

public enum LeaseExpirationBehavior {
    LOG_WARNING,     // Log warning message
    THROW_EXCEPTION, // Throw LeaseExpiredException/SemaphoreLeaseExpiredException
    IGNORE           // Do nothing
}

RateType (from Redisson)

Used by @RateLimit to determine how permits are distributed. This enum is provided by Redisson (org.redisson.api.RateType), not by Locksmith.

// org.redisson.api.RateType
public enum RateType {
    OVERALL,    // Permits shared across all clients/instances (default)
    PER_CLIENT  // Each Redisson client instance gets its own quota
}

Interfaces

LockSkipHandler

public interface LockSkipHandler {
    /**
     * Handle lock acquisition failure.
     *
     * @param context Information about the lock and method
     * @return Value to return from the method, or throw exception
     */
    Object handle(LockContext context);
}

LockContext

import in.riido.locksmith.models.LockContext;

public record LockContext(
    String lockKey,       // Full Redis key
    String methodName,    // Formatted method name
    Method method,        // Reflection Method object
    Object[] args,        // Method arguments
    Class<?> returnType   // Method return type
) { }

SemaphoreSkipHandler

public interface SemaphoreSkipHandler {
    /**
     * Handle permit acquisition failure.
     *
     * @param context Information about the semaphore and method
     * @return Value to return from the method, or throw exception
     */
    Object handle(SemaphoreContext context);
}

SemaphoreContext

import in.riido.locksmith.models.SemaphoreContext;

public record SemaphoreContext(
    String semaphoreKey,  // Full Redis key
    String methodName,    // Formatted method name
    Method method,        // Reflection Method object
    Object[] args,        // Method arguments
    Class<?> returnType,  // Method return type
    String permitId       // Permit ID if acquired, null otherwise
) { }

RateLimitSkipHandler

public interface RateLimitSkipHandler {
    /**
     * Handle rate limit exceeded.
     *
     * @param context Information about the rate limiter and method
     * @return Value to return from the method, or throw exception
     */
    Object handle(RateLimitContext context);
}

RateLimitContext

import in.riido.locksmith.models.RateLimitContext;

public record RateLimitContext(
    String rateLimitKey,  // Full Redis key
    String methodName,    // Formatted method name
    Method method,        // Reflection Method object
    Object[] args,        // Method arguments
    Class<?> returnType   // Method return type
) { }

Exceptions

LockNotAcquiredException

Thrown by LockThrowExceptionHandler when lock acquisition fails.

public class LockNotAcquiredException extends RuntimeException {
    public String getLockKey() { }
    public String getMethodName() { }
}

LeaseExpiredException

Thrown when onLeaseExpired = THROW_EXCEPTION and lock execution exceeds lease time.

public class LeaseExpiredException extends RuntimeException {
    public String getLockKey() { }
    public String getMethodName() { }
    public long getLeaseTimeMs() { }
    public long getExecutionTimeMs() { }
}

SemaphoreNotAcquiredException

Thrown by SemaphoreThrowExceptionHandler when permit acquisition fails.

public class SemaphoreNotAcquiredException extends RuntimeException {
    public String getSemaphoreKey() { }
    public String getMethodName() { }
}

SemaphoreLeaseExpiredException

Thrown when onLeaseExpired = THROW_EXCEPTION and semaphore execution exceeds lease time.

public class SemaphoreLeaseExpiredException extends RuntimeException {
    public String getSemaphoreKey() { }
    public String getMethodName() { }
    public long getLeaseTimeMs() { }
    public long getExecutionTimeMs() { }
}

SemaphoreConfigurationException

Thrown when semaphore configuration is invalid (e.g., same key with different permits).

public class SemaphoreConfigurationException extends RuntimeException {
    public String getSemaphoreKey() { }
}

RateLimitExceededException

Thrown by RateLimitThrowExceptionHandler when rate limit is exceeded.

public class RateLimitExceededException extends RuntimeException {
    public String getRateLimitKey() { }
    public String getMethodName() { }
}

RateLimitConfigurationException

Thrown when rate limiter configuration is invalid (e.g., non-positive permits, zero/negative interval).

public class RateLimitConfigurationException extends RuntimeException {
    public String getRateLimitKey() { }
}

Configuration Properties

public record LocksmithProperties(
    LockProperties lock,
    SemaphoreProperties semaphore,
    RateLimitProperties rateLimit
) { }

public record LockProperties(
    Boolean enabled,     // Default: true - when false, @DistributedLock has no effect
    Duration leaseTime,  // Default: 10 minutes
    Duration waitTime,   // Default: 60 seconds
    String keyPrefix,    // Default: "lock:"
    Boolean debug,       // Default: false
    Boolean metricsEnabled  // Default: false
) { }

public record SemaphoreProperties(
    Boolean enabled,     // Default: true - when false, @DistributedSemaphore has no effect
    Duration leaseTime,  // Default: 5 minutes
    Duration waitTime,   // Default: 60 seconds
    String keyPrefix,    // Default: "semaphore:"
    Boolean debug,       // Default: false
    Boolean metricsEnabled  // Default: false
) { }

public record RateLimitProperties(
    Boolean enabled,     // Default: true - when false, @RateLimit has no effect
    Duration waitTime,   // Default: 60 seconds
    String keyPrefix,    // Default: "ratelimit:"
    Boolean debug,       // Default: false
    Boolean metricsEnabled  // Default: false
) { }

YAML configuration:

locksmith:
  lock:
    enabled: true
    lease-time: 10m
    wait-time: 60s
    key-prefix: "lock:"
    debug: false
    metrics-enabled: false
  semaphore:
    enabled: true
    lease-time: 5m
    wait-time: 60s
    key-prefix: "semaphore:"
    debug: false
    metrics-enabled: false
  rate-limit:
    enabled: true
    wait-time: 60s
    key-prefix: "ratelimit:"
    debug: false
    metrics-enabled: false

See: Configuration


Complete Examples

Lock Example

@Service
public class OrderService {

    @DistributedLock(
        key = "#{'order-' + #orderId}",
        type = LockType.REENTRANT,
        mode = AcquisitionMode.WAIT_AND_SKIP,
        waitTime = "30s",
        leaseTime = "10m",
        skipHandler = LockThrowExceptionHandler.class,
        onLeaseExpired = LeaseExpirationBehavior.THROW_EXCEPTION
    )
    public Order processOrder(String orderId) {
        // Process with all options specified
        return doProcess(orderId);
    }

    // Minimal form with defaults
    @DistributedLock(key = "#{'order-' + #orderId}")
    public Order processOrderSimple(String orderId) {
        return doProcess(orderId);
    }
}

Semaphore Example

@Service
public class ApiService {

    @DistributedSemaphore(
        key = "#{'api-' + #region}",
        permits = 10,
        mode = AcquisitionMode.WAIT_AND_SKIP,
        waitTime = "30s",
        leaseTime = "5m",
        skipHandler = SemaphoreThrowExceptionHandler.class,
        onLeaseExpired = LeaseExpirationBehavior.LOG_WARNING
    )
    public Response callApi(String region) {
        // Call with all options specified
        return doApiCall(region);
    }

    // Minimal form with defaults
    @DistributedSemaphore(key = "api-pool", permits = 5)
    public Response callApiSimple() {
        return doApiCall("default");
    }
}

Rate Limit Example

@Service
public class RateLimitedService {

    @RateLimit(
        key = "#{'user-' + #userId}",
        permits = 100,
        interval = "1m",
        type = RateType.OVERALL,
        mode = AcquisitionMode.WAIT_AND_SKIP,
        waitTime = "5s",
        skipHandler = RateLimitThrowExceptionHandler.class
    )
    public Response processUserRequest(String userId) {
        // Call with all options specified
        return doProcess(userId);
    }

    // Minimal form with defaults (10 permits per second)
    @RateLimit(key = "api-endpoint")
    public Response processRequestSimple() {
        return doProcess("default");
    }
}

Clone this wiki locally