How to Implement a Request Rate Limiter in Spring Boot Using Custom Annotations

This article demonstrates a complete Spring Boot solution for limiting how many times a specific API can be called by a user within a given time window, using a custom @RequestLimit annotation, Redis for counting, and an interceptor to enforce the rule.

Java High-Performance Architecture
Java High-Performance Architecture
Java High-Performance Architecture
How to Implement a Request Rate Limiter in Spring Boot Using Custom Annotations

In many projects you may need to restrict a certain interface so that a specific user can only request it N times within a defined period.

The common problem of repeated button clicks can be solved on the backend by recording request counts in Redis; if the count exceeds the limit, the request is denied. Redis keys have an expiration time, so they are automatically removed after the window.

Principle

When a request arrives, the server increments a Redis key that records the number of accesses. If the count exceeds the configured maximum, the request is blocked. The key expires after the configured number of seconds.

Code Implementation

To make the solution look more sophisticated, it is implemented with a custom annotation.

@RequestLimit Annotation

import java.lang.annotation.*;

/**
 * Custom annotation for request limiting
 */
@Documented
@Inherited
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestLimit {
    // Maximum number of requests allowed within 'second' seconds
    int second() default 1;
    int maxCount() default 1;
}

RequestLimitIntercept Interceptor

The interceptor checks the request count before the controller method is executed.

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import top.lrshuai.limit.annotation.RequestLimit;
import top.lrshuai.limit.common.ApiResultEnum;
import top.lrshuai.limit.common.Result;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/** Request interceptor */
@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)) {
                    responseOut(response, Result.error(ApiResultEnum.REQUST_LIMIT));
                    return false;
                }
            }
        }
        return super.preHandle(request, response, handler);
    }

    // Determine whether the request exceeds the limit
    public boolean isLimit(HttpServletRequest request, RequestLimit requestLimit) {
        // Use session ID as a unique key for demonstration; in production you may use user ID
        String limitKey = request.getServletPath() + request.getSession().getId();
        Integer redisCount = (Integer) redisTemplate.opsForValue().get(limitKey);
        if (redisCount == null) {
            // First request, set initial count with expiration
            redisTemplate.opsForValue().set(limitKey, 1, requestLimit.second(), TimeUnit.SECONDS);
        } else {
            if (redisCount.intValue() >= requestLimit.maxCount()) {
                return true;
            }
            // Increment count
            redisTemplate.opsForValue().increment(limitKey);
        }
        return false;
    }

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

WebMvcConfig Registration

For Spring Boot 2.x, implement WebMvcConfigurer and add the interceptor.

@Slf4j
@Component
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private RequestLimitIntercept requestLimitIntercept;

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

Controller Usage

Apply the annotation on a class or method. Method-level parameters take precedence over class-level ones.

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

    /** Limit on method, overrides class settings */
    @GetMapping("/test1")
    @RequestLimit
    public Result test() {
        return Result.ok();
    }

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

By default, each interface can be called only once per second (maxCount=1, second=1). Adjust maxCount and second as needed.

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.

JavaredisSpring BootCustom AnnotationInterceptor
Java High-Performance Architecture
Written by

Java High-Performance Architecture

Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.

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.