# Summer Ratelimit

> Summer Framework rate limiting (v0.2.2+ ONLY) — fixed-window, sliding-window, token-bucket strategies with Redis or in-memory storage. acquire() for auto-429, tryAcquire() for manual control. Use when implementing rate limiting in Summer Framework projects — per-user, per-IP, per-tenant, or global rate limits with RateLimiterService. Includes distributed Redis patterns and multi-tenant tiered policy configuration.

- Skill: `taipt1504/summer-ratelimit` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add taipt1504/summer-ratelimit`
- Raw SKILL.md: https://api.skillmd.com/api/skills/taipt1504/summer-ratelimit/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: taipt1504 (https://skillmd.com/u/taipt1504)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/taipt1504/summer-ratelimit

---


# Summer Rate Limiting — v0.2.2+ Only

**Gate:** Verify summer-core loaded and `io.f8a.summer:summer-platform` in build.gradle before proceeding.

**Module:** `summer-ratelimit-autoconfigure` | **Config:** `f8a.rate-limiter` | **Prereq:** Summer >= 0.2.2 only.

```gradle
implementation 'io.f8a.summer:summer-ratelimit-autoconfigure'
```

## Three Strategies

| Strategy | Description |
|---|---|
| `fixed-window` | Resets counter at fixed intervals |
| `sliding-window` | Rolling time window for smoother limits |
| `token-bucket` | Refills tokens at steady rate; handles bursts |

## Usage

### `acquire()` — throws 429 automatically

```java
@RequiredArgsConstructor
public class OrderController {
    private final RateLimiterService rateLimiterService;

    public Mono<Order> createOrder(String userId, CreateOrderRequest req) {
        return rateLimiterService
            .acquire(new RateLimitKey(userId, "orders:create"))
            .then(orderService.create(req));
    }

    // Custom limit/window at call site (still uses scope's strategy)
    public Mono<Void> sendOtp(String ip) {
        return rateLimiterService
            .acquire(new RateLimitKey(ip, "auth:otp"), 3, Duration.ofMinutes(10))
            .then(otpService.send());
    }
}
```

429 error response:
```json
{ "code": "com.rate.limit.exceeded", "details": [
    { "field": "limit", "value": "10" },
    { "field": "resetAt", "value": "1741123456" }
]}
```

### `tryAcquire()` — returns `RateLimitResult` for manual handling

```java
public Mono<Order> getOrder(String userId, String orderId, ServerWebExchange exchange) {
    return rateLimiterService
        .tryAcquire(new RateLimitKey(userId, "orders:read"))
        .flatMap(result -> {
            exchange.getResponse().getHeaders().set("X-RateLimit-Remaining",
                String.valueOf(result.remaining()));
            if (!result.allowed()) {
                return Mono.error(CommonExceptions.RATE_LIMIT_EXCEEDED.toException());
            }
            return orderService.getById(orderId);
        });
}
```

`RateLimitResult` record: `allowed()`, `limit()`, `remaining()`, `resetAt()`.

## Types

| Type | Package | Purpose |
|---|---|---|
| `RateLimiterService` | `core.ratelimit` | Main service: `acquire()` / `tryAcquire()` |
| `RateLimitKey` | `core.ratelimit` | Key record: `identifier` + `policyKey` |
| `RateLimitResult` | `core.ratelimit` | Result record: `allowed`, `limit`, `remaining`, `resetAt` |

## Configuration

```yaml
f8a:
  rate-limiter:
    key-prefix: "ratelimit:"            # global storage key prefix
    storage-type: redis                 # "redis" (default) or "memory"
    default-policy:                     # fallback for unmatched scopes
      strategy: token-bucket
      limit: 100
      window: 60s
      token-bucket-refill-rate: 0       # tokens/sec (0 = auto: limit/windowSec)
    policies:                           # named per-scope policies
      users:read:
        strategy: sliding-window
        limit: 1000
        window: 3600s
      orders:create:
        strategy: fixed-window
        limit: 10
        window: 60s
      auth:otp:
        strategy: fixed-window
        limit: 5
        window: 300s
```

## Storage

- **Redis** (default): Atomic Lua scripts. Auto-detected when `spring-boot-starter-data-redis-reactive` on classpath.
- **Memory**: In-process fallback (`storage-type: memory`). Dev/testing only.

WARNING: `storage-type: memory` NOT cluster-safe, resets on restart. Use Redis in production — in-memory silently produces incorrect limits in multi-instance deployments.

## Strategy Override

`strategy` is per named policy — not overridable at call site. Only `limit` and `window` can be overridden via `acquire(key, limit, window)` / `tryAcquire(key, limit, window)`.

See `references/policy-examples.md` for per-user, per-IP, per-endpoint examples.

## Rules

- Use `acquire()` for standard endpoints — auto-429 safer than manual handling
- Redis storage in production — `memory` NOT cluster-safe
- Named policies for auth/write scopes — never rely solely on default-policy
- Never override strategy at call site — per-policy only; `limit` and `window` are overridable
- Verify Summer >= 0.2.2 — module absent in earlier versions

## Related Skills

- **summer-core** — CommonExceptions.RATE_LIMIT_EXCEEDED for manual error handling
- **summer-rest** — Controllers using RateLimiterService with BaseController pattern
- **redis-patterns** — Redis configuration for rate limit storage backend
- **spring-security** — Rate limiting as part of security defense-in-depth

