
Stop One User From Crashing Your Spring Boot API
One bad script can crash your whole API. Learn the token bucket algorithm and add rate limiting to Spring Boot with Bucket4j in three steps.
Series: How to complete certification... (5 articles)
- …
- 4Stop One User From Crashing Your Spring Boot API This article
Level: Beginner  | Reading time: 6 minutes  | Stack: Spring Boot, Bucket4j
One user with a bad script can send a thousand requests a second and take your whole API down. Here is how a nightclub bouncer with a bowl of tokens explains the fix.
1. The Problem
Your API has no doorman. Every request that arrives gets in, no matter how fast they come. One retry loop in someone's broken code, or one deliberate attack, and your database connections, CPU and memory are gone, for every user, not just the one causing it.
1
2
for i in range(100000):
requests.get("https://your-api.com/search")Run that against most beginner APIs and the server falls over in seconds. The fix is not to write more careful code. It is to put a doorman in front of the API who decides, request by request, whether there is room.
Key ideaDo not let everyone in at once. Hand out a limited number of tokens, refill them over time, and only let someone through if they are holding one.
2. The Cast
- The client is the guest. It wants to get past the rope and reach the API.
- The bouncer is the rate limiter. It checks for a token before anyone passes.
- The token bucket is the bowl of tokens. It starts full, empties as guests enter, and refills at a steady rate.
- Your API is the club. It only ever deals with guests who were let in.
This is a real algorithm, not just an analogy. It is called the token bucket, and it is what libraries like Bucket4j implement.
3. One Minute, Four Requests

The first three get in, each taking one token. The fourth arrives before the bucket refills, finds it empty, and is turned away with a 429 status code. A moment later, the bucket refills and the next guest gets in normally.
4. Build It in Three Steps
Step 1. Add the dependency. Bucket4j 8.7 is the current stable core library, and it works with plain Java, so it drops into any Spring Boot version:
1
2
3
4
5
<dependency>
<groupId>com.bucket4j</groupId>
<artifactId>bucket4j-core</artifactId>
<version>8.7.0</version>
</dependency>Step 2. Create a bucket per client. A real app should key this by user ID or API key, not by one shared bucket for everyone:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket;
import io.github.bucket4j.Refill;
import java.time.Duration;
import java.util.concurrent.ConcurrentHashMap;
public class RateLimiter {
private final ConcurrentHashMap<String, Bucket> buckets = new ConcurrentHashMap<>();
public Bucket resolveBucket(String clientId) {
return buckets.computeIfAbsent(clientId, id -> createBucket());
}
private Bucket createBucket() {
Bandwidth limit = Bandwidth.classic(3,
Refill.greedy(3, Duration.ofMinutes(1)));
return Bucket.builder().addLimit(limit).build();
}
}This allows 3 requests, refilled at a steady 3 per minute. Adjust the numbers to fit your API.
Step 3. Check the bucket in a filter. A servlet filter is the cleanest place, since it runs before your controller:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import io.github.bucket4j.Bucket;
import io.github.bucket4j.ConsumptionProbe;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public class RateLimitFilter implements Filter {
private final RateLimiter limiter = new RateLimiter();
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpReq = (HttpServletRequest) req;
HttpServletResponse httpRes = (HttpServletResponse) res;
String clientId = httpReq.getRemoteAddr();
Bucket bucket = limiter.resolveBucket(clientId);
ConsumptionProbe probe = bucket.tryConsumeAndReturnRemaining(1);
if (probe.isConsumed()) {
httpRes.addHeader("X-Rate-Limit-Remaining", String.valueOf(probe.getRemainingTokens()));
chain.doFilter(req, res);
} else {
httpRes.setStatus(429);
httpRes.addHeader("Retry-After", String.valueOf(probe.getNanosToWaitForRefill() / 1_000_000_000));
httpRes.getWriter().write("Too many requests. Try again shortly.");
}
}
}Register it in your Spring Security filter chain, or as a plain @Component with @Bean FilterRegistrationBean, ahead of your controllers.
5. One Server vs Many
- One instance. The in-memory setup above is enough. Each bucket lives in that one server's memory.
- Multiple instances. If your app runs behind a load balancer on more than one server, an in-memory bucket is wrong, because each server would track its own count and a client could get three times the real limit. Back Bucket4j with Redis so every instance shares the same bucket.
- At the edge. For simple, app-wide limits, Amazon API Gateway can throttle requests before they ever reach Spring Boot. Use Bucket4j when the limit needs to vary per user or per plan.
6. Four Pitfalls to Avoid
- One shared bucket for every client. This punishes everyone for one user's traffic. Key buckets by user ID, API key or IP address.
- Limiting by IP alone. Many real users can share one IP behind a corporate network or mobile carrier. Prefer an API key or user ID when you have one.
- No Retry-After header. Without it, a well-behaved client has no way to know when to try again, and may just retry immediately, making things worse.
- Forgetting the distributed case. An in-memory bucket on each server quietly multiplies your real limit once you scale past one instance.
Takeaway
Before your API goes live, ask: what stops one client from calling this a thousand times a second? A token bucket answers that in a few lines of code, and it protects every other user on the system.
Let's Support Each Other
If this article helped you, please like it and leave a comment. I will do my best to return the favour on your articles and posts, though replies may take a little time.
We are all working towards the 21-badge milestone, and genuine engagement makes that easier for everyone. Please read each other's articles, leave meaningful comments, and vote for good Wishlist ideas. I only have 5 Wishlist votes per day, so I cannot vote for everyone.
Learn, Build, Share, Connect, Earn, Get Certified. If that goal resonates with you, a vote means a lot. Thank you, and good luck with your own AWS goals.
@lley_candy signing off :)
#springboot #java #bucket4j #backend #api #AWSStudentRewards #AWSBuilderCenter #AWSCommunity #CloudComputing #AWSCertification
Series: How to complete certification... (5 articles)
- …
- 4Stop One User From Crashing Your Spring Boot API This article
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article