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.

Architect's Guide
Architect's Guide
Architect's Guide
Implementing Request Rate Limiting in Spring Boot Using a Custom Annotation and Interceptor

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.

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 BootInterceptorannotationrate limiting
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.