How to Implement Rate Limiting in Spring Boot with Custom Annotations and Redis
This article explains how to restrict the number of requests to a Spring Boot API within a given time window by using a custom @RequestLimit annotation, a Redis‑backed interceptor, and the necessary configuration to register the interceptor in the application.
In many projects you may need to ensure that a specific API endpoint can be called only N times within a certain period, preventing duplicate submissions or button‑spamming.
Principle
When a request arrives, the server records the request count in Redis; if the count exceeds the configured limit, the request is rejected. The Redis key is set with an expiration time so it is automatically removed after the window expires.
Code Implementation
RequestLimit Annotation
import java.lang.annotation.*;
@Documented
@Inherited
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestLimit {
/** maximum number of requests allowed within the time window */
int maxCount() default 1;
/** time window in seconds */
int second() default 1;
}RequestLimitIntercept Interceptor
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 instanceof HandlerMethod) {
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 && isLimit(request, requestLimit)) {
resonseOut(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) {
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 >= requestLimit.maxCount()) {
return true;
}
redisTemplate.opsForValue().increment(limitKey);
}
return false;
}
/** Write JSON response back to the client */
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 Configuration
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private RequestLimitIntercept requestLimitIntercept;
@Override
public void addInterceptors(InterceptorRegistry registry) {
log.info("Adding RequestLimit interceptor");
registry.addInterceptor(requestLimitIntercept);
}
}Controller Example
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/index")
@RequestLimit(maxCount = 5, second = 1) // class‑level limit
public class IndexController {
@GetMapping("/test1")
@RequestLimit(maxCount = 5, second = 1) // method‑level limit (takes precedence)
public Result test() {
// TODO: business logic
return Result.ok();
}
@GetMapping("/test2")
public Result test2() {
// TODO: business logic
return Result.ok();
}
}When both class and method are annotated with @RequestLimit, the method‑level parameters override the class‑level ones. By default, the limit allows one request per second per endpoint.
Full 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
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.
