Implementing Request Rate Limiting in Spring Boot Using a Custom Annotation and Interceptor
This article explains how to limit the number of requests an API endpoint can receive within a specified time window by creating a @RequestLimit annotation, a Redis‑backed interceptor, and the necessary Spring Boot configuration and controller examples.
The article describes a common requirement: restricting a specific API endpoint so that a single user can only call it N times within a given period, which is useful for preventing duplicate submissions or button‑spamming.
Principle : Each request is recorded in Redis; if the stored count exceeds the configured limit, the request is denied. Redis keys have an expiration time, so the count automatically resets after the time window.
Custom Annotation (@RequestLimit) :
import java.lang.annotation.*;
/**
* Request limit custom annotation
*/
@Documented
@Inherited
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestLimit {
// maximum requests allowed within <second> seconds
int second() default 1;
int maxCount() default 1;
}Interceptor (RequestLimitIntercept) checks the annotation before the controller method executes, builds a unique Redis key (using the request path and session ID), reads the current count, increments it, and returns an error response when the limit is reached.
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;
@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) {
String limitKey = request.getServletPath() + request.getSession().getId();
Integer redisCount = (Integer) redisTemplate.opsForValue().get(limitKey);
if (redisCount == null) {
redisTemplate.opsForValue().set(limitKey, 1, requestLimit.second(), TimeUnit.SECONDS);
} else {
if (redisCount.intValue() >= requestLimit.maxCount()) {
return true;
}
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");
String json = JSONObject.toJSON(result).toString();
PrintWriter out = response.getWriter();
out.append(json);
}
}WebMvcConfig registers the interceptor. For Spring Boot 2.x it implements WebMvcConfigurer; for 1.x it extends WebMvcConfigurerAdapter.
@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 : The @RequestLimit annotation can be placed on a class or on individual methods. When both are present, method‑level parameters take precedence.
@RestController
@RequestMapping("/index")
@RequestLimit(maxCount = 5, second = 1)
public class IndexController {
@GetMapping("/test1")
@RequestLimit
public Result test() {
return Result.ok();
}
@GetMapping("/test2")
public Result test2() {
return Result.ok();
}
}The complete source code is available on Gitee and GitHub (links provided in the original article). The author also includes a brief promotional note encouraging readers to star the repository.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architect's Guide
Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
