Backend Development 11 min read

Implementing Rate Limiting in Java Spring Applications Using Guava, Redis, and Nginx

This article explains why rate limiting is needed for high‑traffic Java services, reviews common throttling techniques such as Hystrix, Sentinel, token‑bucket algorithms, and then provides multiple concrete implementations—including Guava RateLimiter, Redis counters, interceptor configuration, and Tomcat connector settings—complete with code samples.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Implementing Rate Limiting in Java Spring Applications Using Guava, Redis, and Nginx

1. Rate Limiting Operations

Why Rate Limit

Rate limiting prevents malicious rapid refreshes of an interface, protects services that run on external servers without hardware upgrades, and avoids crashes caused by excessive concurrent requests.

Common Rate‑Limiting Methods

Netflix Hystrix

Alibaba Sentinel (open‑source)

Queue, thread pool, message queue (Kafka), middleware, and Sentinel strategies such as direct reject, warm‑up, and smooth queuing

Technical approaches include caching duplicate requests, using load balancers like Nginx, caching hot data in Redis or Elasticsearch, and employing connection pools.

Business approaches may add user interaction queues.

2. Application‑Level Rate Limiting Implementations

Method 1 – Google Guava Token‑Bucket : Use Guava's RateLimiter to implement SmoothBursty or SmoothWarmingUp throttling.

<!-- Java project core library dependency -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>23.0</version>
</dependency>
package com.citydo.dialogue.controller;

import com.google.common.util.concurrent.RateLimiter;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HomeController {
    // 10 permits per second
    private RateLimiter limiter = RateLimiter.create(10.0);

    @GetMapping("/test/{name}")
    public String test(@PathVariable("name") String name) {
        double acquire = limiter.acquire();
        System.out.println("--------" + acquire);
        if (acquire >= -1e-6 && acquire <= 1e-6) {
            return name;
        } else {
            return "操作太频繁"; // Too frequent
        }
    }
}

This behaves like QPS control: when the request rate exceeds a threshold, the limiter blocks or rejects excess calls.

Method 2 – Redis Counter : Increment a Redis key (e.g., IP+timestamp) on each request and set an expiration time.

package com.citydo.dialogue.config;

import com.citydo.dialogue.service.AccessLimitInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AccessLimitInterceptor())
                .addPathPatterns("/**");
    }
}
package com.citydo.dialogue.service;

import com.citydo.dialogue.entity.AccessLimit;
import com.citydo.dialogue.utils.IpUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.text.SimpleDateFormat;
import java.util.concurrent.TimeUnit;

public class AccessLimitInterceptor implements HandlerInterceptor {
    @Autowired
    private RedisTemplate
redisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod hm = (HandlerMethod) handler;
            AccessLimit al = hm.getMethodAnnotation(AccessLimit.class);
            if (al == null) return true;
            int limit = al.limit();
            int sec = al.sec();
            String key = IpUtil.getIpAddr(request) + request.getRequestURI();
            Integer count = redisTemplate.opsForValue().get(key);
            if (count == null) {
                redisTemplate.opsForValue().set(key, 1, sec, TimeUnit.SECONDS);
            } else if (count < limit) {
                redisTemplate.opsForValue().set(key, count + 1, sec, TimeUnit.SECONDS);
            } else {
                output(response, "请求太频繁!");
                return false;
            }
        }
        return true;
    }

    private void output(HttpServletResponse response, String msg) throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        response.getOutputStream().write(msg.getBytes("UTF-8"));
    }
}

The interceptor can be driven by a custom annotation:

import java.lang.annotation.*;

@Inherited
@Documented
@Target({ElementType.FIELD, ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {
    int limit() default 5; // max requests
    int sec() default 5;   // time window in seconds
}

Redis configuration to avoid serialization issues:

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate
redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate
template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setHashKeySerializer(new GenericJackson2JsonRedisSerializer());
        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }
}

Method 3 – Distributed Rate Limiting : Use Redis+Lua scripts or Nginx+Lua for atomic throttling across multiple nodes.

Method 4 – Pooling Techniques : Limit total resources such as database connections or thread pools; excess requests wait or throw exceptions.

Method 5 – Tomcat Connector Settings (example parameters):

maxThreads : maximum request‑handling threads

maxConnections : peak simultaneous connections, excess are queued

acceptCount : queue size for incoming connections when all threads are busy

Method 6 – AtomicLong Counter for a single endpoint:

try {
    if (atomic.incrementAndGet() > limit) {
        // reject request
    } else {
        // process request
    }
} finally {
    atomic.decrementAndGet();
}

Method 7 – Guava Cache with Time Window :

LoadingCache
counter = CacheBuilder.newBuilder()
    .expireAfterWrite(2, TimeUnit.SECONDS)
    .build(new CacheLoader
() {
        @Override
        public AtomicLong load(Long seconds) {
            return new AtomicLong(0);
        }
    });
long limit = 1000;
while (true) {
    long currentSeconds = System.currentTimeMillis() / 1000;
    if (counter.get(currentSeconds).incrementAndGet() > limit) {
        System.out.println("限流了: " + currentSeconds);
        continue;
    }
    // business logic
}

All the above techniques aim to control request throughput and protect backend services from overload.

References: CSDN article, WeChat public‑account posts, and the GitHub repository springboot_current_limiting .

backendJavaRedisSpring BootGuavarate limitingthrottling
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login 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.