Master Rate Limiting in Spring Boot with Bucket4j: Token Bucket Guide
This tutorial explains how to implement API rate limiting in Spring Boot using the Bucket4j library, covering token‑bucket theory, Maven dependencies, Redis configuration, YAML rules, annotation‑based limits, custom responses, and conditional bypasses with complete code examples.
1. Introduction
Rate limiting is a strategy that restricts the number of API calls a client can make within a certain time window, protecting the service from accidental or malicious overuse. It is typically enforced by tracking IP addresses, API keys, or tokens. When a client exceeds the limit, you can either queue the request until the window resets or reject it with HTTP 429 (Too Many Requests).
This article introduces the open‑source component Bucket4j , which provides powerful rate‑limiting based on the token‑bucket algorithm. It works for standalone JVM applications as well as clustered environments and supports in‑memory or distributed caches via the JCache (JSR‑107) specification.
2. Practical Example
2.1 Environment Setup
Add the following Maven dependencies:
<code><dependency></code><code> <groupId>com.giffing.bucket4j.spring.boot.starter</groupId></code><code> <artifactId>bucket4j-spring-boot-starter</artifactId></code><code> <version>0.12.7</version></code><code></dependency></code><code><dependency></code><code> <groupId>com.bucket4j</groupId></code><code> <artifactId>bucket4j-redis</artifactId></code><code> <version>8.10.1</version></code><code></dependency></code><code><dependency></code><code> <groupId>redis.clients</groupId></code><code> <artifactId>jedis</artifactId></code><code></dependency></code><code><dependency></code><code> <groupId>io.micrometer</groupId></code><code> <artifactId>micrometer-core</artifactId></code><code></dependency></code>Configure a JedisPool bean:
<code>@Bean</code><code>public JedisPool jedisPool(@Value("${spring.data.redis.port}") Integer port,</code><code> @Value("${spring.data.redis.host}") String host,</code><code> @Value("${spring.data.redis.password}") String password,</code><code> @Value("${spring.data.redis.database}") Integer database) {</code><code> // buildPoolConfig() should be implemented by you</code><code> final JedisPoolConfig poolConfig = buildPoolConfig();</code><code> return new JedisPool(poolConfig, host, port, 60000, password, database);</code><code>}</code>With the environment ready, you can define the API and apply rate‑limiting rules.
2.2 Configuration‑File Based Limiting
Define a simple ProductController :
<code>@RestController</code><code>@RequestMapping("/products")</code><code>public class ProductController {</code><code> @GetMapping("/{id}")</code><code> public Product getProduct(@PathVariable Integer id) {</code><code> return new Product(id, "商品 - " + id, BigDecimal.valueOf(new Random().nextDouble(1000)));</code><code> }</code><code>}</code>Add the following YAML configuration (under bucket4j ) to limit each id to 2 requests per 30 seconds:
<code>bucket4j:</code><code> cache-to-use: redis-jedis</code><code> filter-config-caching-enabled: true</code><code> filters:</code><code> - cache-name: product_cache_name</code><code> id: product_filter</code><code> url: /products/.*</code><code> rate-limits:</code><code> - cache-key: getParameter("id")</code><code> bandwidths:</code><code> - capacity: 2</code><code> time: 30</code><code> unit: seconds</code><code> refill-speed: interval</code>When the limit is exceeded, the default response is HTTP 429. You can customize the response body and content type:
<code>bucket4j:</code><code> filters:</code><code> - cache-name: product_cache_name</code><code> http-content-type: 'application/json;charset=utf-8'</code><code> http-response-body: '{"code": -1, "message": "请求太快了"}'</code>2.3 Annotation‑Based Limiting
Use the @RateLimiting annotation (AOP) to intercept methods. Example configuration for a method‑level limit:
<code>bucket4j:</code><code> methods:</code><code> - name: storage_rate</code><code> cache-name: storage_cache_name</code><code> rate-limit:</code><code> bandwidths:</code><code> - capacity: 2</code><code> time: 30</code><code> unit: seconds</code><code> refill-speed: interval</code>Apply the annotation to a controller method:
<code>@RateLimiting(</code><code> name = "storage_rate",</code><code> cacheKey = "'storage-' + #id",</code><code> skipCondition = "#name eq 'admin'",</code><code> ratePerMethod = true,</code><code> fallbackMethodName = "getStorageFallback"</code><code>)</code><code>@GetMapping("/{id}")</code><code>public R<Storage> getStorage(@PathVariable Integer id, String name) {</code><code> return R.success(new Storage(id, "SP001 - " + id, new Random().nextInt(10000)));</code><code>}</code><code>public R<Storage> getStorageFallback(Integer id, String name) {</code><code> return R.failure(String.format("请求id=%d,name=%s被限流", id, name));</code><code>}</code>The skipCondition attribute lets you bypass the limit when the request meets a specific condition (e.g., name == "admin" ).
You can also place @RateLimiting on a class so that all its methods are limited, and use @IgnoreRateLimiting on individual methods to exclude them.
Images
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
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.