Implementing Spring Boot API Rate Limiting with Redis and Custom Annotations

This article demonstrates how to implement API rate limiting in Spring Boot using Redis for request counting and a custom @RequestLimit annotation with an interceptor to enforce limits per user session, including complete code examples for the annotation, interceptor, configuration, and controller usage.

Architect's Guide
Architect's Guide
Architect's Guide
Implementing Spring Boot API Rate Limiting with Redis and Custom Annotations

Principle

The rate limiting mechanism uses Redis to record the number of requests made by a client within a specified time window. Each request increments a counter stored under a key that combines the request path and the client's session ID (or user ID for mobile apps). The key expires after the configured number of seconds, automatically resetting the count.

Custom Annotation @RequestLimit

The annotation defines two parameters: second (time window in seconds, default 1) and maxCount (maximum allowed requests within that window, default 1). It can be applied to both methods and controller classes.

import java.lang.annotation.*;

/**
 * Request limit custom annotation
 */
@Documented
@Inherited
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestLimit {
    // Within 'second' seconds, maximum 'maxCount' requests allowed
    int second() default 1;
    int maxCount() default 1;
}

Interceptor RequestLimitIntercept

The interceptor extends HandlerInterceptorAdapter (Spring Boot 2.x) and overrides preHandle. It checks whether the handler is a HandlerMethod, then looks for the @RequestLimit annotation on the method first, falling back to the class-level annotation. If found, it calls isLimit to evaluate the request count against Redis.

@Slf4j
@Component
public class RequestLimitIntercept extends HandlerInterceptorAdapter {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler.getClass().isAssignableFrom(HandlerMethod.class)) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            RequestLimit methodAnnotation = method.getAnnotation(RequestLimit.class);
            RequestLimit classAnnotation = method.getDeclaringClass().getAnnotation(RequestLimit.class);
            RequestLimit requestLimit = methodAnnotation != null ? methodAnnotation : classAnnotation;
            if (requestLimit != null) {
                if (isLimit(request, requestLimit)) {
                    resonseOut(response, Result.error(ApiResultEnum.REQUST_LIMIT));
                    return false;
                }
            }
        }
        return super.preHandle(request, response, handler);
    }

    public boolean isLimit(HttpServletRequest request, RequestLimit requestLimit) {
        // Key combines servlet path and session ID (use user ID for apps)
        String limitKey = request.getServletPath() + request.getSession().getId();
        Integer redisCount = (Integer) redisTemplate.opsForValue().get(limitKey);
        if (redisCount == null) {
            // First request: initialize counter with TTL
            redisTemplate.opsForValue().set(limitKey, 1, requestLimit.second(), TimeUnit.SECONDS);
        } else {
            if (redisCount.intValue() >= requestLimit.maxCount()) {
                return true; // limit exceeded
            }
            // Increment counter
            redisTemplate.opsForValue().increment(limitKey);
        }
        return false;
    }

    private void resonseOut(HttpServletResponse response, Result result) throws IOException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        PrintWriter out = response.getWriter();
        String json = JSONObject.toJSON(result).toString();
        out.append(json);
    }
}

WebMvcConfig Configuration

Register the interceptor by implementing WebMvcConfigurer (Spring Boot 2.x) and overriding addInterceptors. For Spring Boot 1.x, extend WebMvcConfigurerAdapter instead.

@Slf4j
@Component
public class WebMvcConfig implements WebMvcConfigurer {
    @Autowired
    private RequestLimitIntercept requestLimitIntercept;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        log.info("Adding interceptor");
        registry.addInterceptor(requestLimitIntercept);
    }
}

Controller Usage

The annotation can be placed on the controller class (applying to all endpoints) or on individual handler methods. Method-level annotation takes precedence over class-level.

Class-level: @RequestLimit(maxCount = 5, second = 1) on the controller class.

Method-level: @RequestLimit on a specific @GetMapping method (uses default 1 request per second).

@RestController
@RequestMapping("/index")
@RequestLimit(maxCount = 5, second = 1)
public class IndexController {

    /**
     * Method-level annotation overrides class parameters
     */
    @GetMapping("/test1")
    @RequestLimit
    public Result test() {
        return Result.ok();
    }

    /**
     * Uses class-level annotation parameters
     */
    @GetMapping("/test2")
    public Result test2() {
        return Result.ok();
    }
}

Repository Links

Complete source code is available at:

Gitee:

https://gitee.com/rstyro/spring-boot/tree/master/SpringBoot-limit

GitHub:

https://github.com/rstyro/Springboot/tree/master/SpringBoot-limit
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendJavaRedisSpring BootCustom AnnotationInterceptorRate LimitingAPI Security
Architect's Guide
Written by

Architect's Guide

Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.