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.
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-limitGitHub:
https://github.com/rstyro/Springboot/tree/master/SpringBoot-limitSigned-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.
