# Bucket4j: a Java token-bucket rate limiter that scales from one JVM to a cluster

> Bucket4j is an Apache-2.0 Java library that enforces request limits with integer-arithmetic token buckets, and the same bucket API can be backed by Hazelcast, Redis, or a database. It expects you to write the limiting code yourself.

**bucket4j/bucket4j** — Java rate limiting library based on token-bucket algorithm.

- Repository: https://github.com/bucket4j/bucket4j
- Website: https://bucket4j.com
- Stars: 2,804 · Forks: 328
- Language: Java
- License: Apache-2.0
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/bucket4j-bucket4j

## The problem Bucket4j solves, and who ends up using it

Most Java services eventually need to say no to a caller. An API endpoint should accept ten requests per second from one client and reject the eleventh, and the rejection has to survive concurrency: two threads asking at the same instant must not both see a full bucket. Bucket4j implements the token-bucket algorithm for exactly that decision. The README describes it as a Java rate-limiting library based on the token-bucket algorithm, and the quick start reduces the whole thing to one call, bucket.tryConsume(1), whose boolean result you branch on.

The audience is Java developers who want the limiter as a component inside their own service rather than as a separate proxy. The README is explicit that Bucket4j is not a framework: you write the code that decides what happens when a token cannot be consumed. That is a deliberate boundary. If you want limits declared in application.yaml and applied by an interceptor, the README points to a third-party Spring Boot starter instead, and notes that its key advantage is configuration through properties or yaml files rather than hand-written code.

The second audience is teams running several JVMs behind a load balancer. A local in-memory bucket per instance gives you an effective limit multiplied by the number of instances, which is usually not what was intended. Bucket4j addresses this with distributed back-ends, and that is where the module list in the repository gets long.

## How the token bucket actually works in Bucket4j

A bucket holds tokens up to a capacity, and refills at a configured rate. Each protected operation consumes tokens; when the bucket is empty, tryConsume returns false and the caller decides what to do. The README's example builds a bucket with capacity 20 and a greedy refill of 10 tokens per minute.

Two design choices are worth calling out because they shape behaviour. First, the README states that Bucket4j does not operate with floats or doubles and performs all calculations in integer arithmetic, which it presents as protection from rounding errors. In practice this means refill timing is expressed in whole tokens and durations, not fractional rates. Second, the default concurrency strategy is lock-free, and the README says other concurrency strategies can be selected when the lock-free default is not desired. That default matters under contention: a lock-free path avoids blocking threads, but it is a choice you can override rather than a fixed property of the library.

For distributed use, the bucket state lives outside the JVM. The README lists JCache (JSR 107) as the generic path, plus dedicated integrations for Hazelcast, Apache Ignite, Infinispan, Oracle Coherence, Couchbase and Apache Geode (GemFire), and Redis back-ends through Vert.x, Redisson, Jedis and Lettuce. These are not equivalent. The comparison table in the README marks async support, flexible per-entry expiration, optimized serialization and thin-client support separately for each back-end, and the columns differ: Hazelcast and Infinispan support flexible per-entry expiration, Apache Ignite does not, and JCache supports none of the four. Picking a back-end is therefore a real decision, not a formality.

## Installing Bucket4j from Maven Central and limiting one method

Bucket4j is distributed through Maven Central. For Java 17 and later, the README gives this dependency, with groupId com.bucket4j and artifactId bucket4j_jdk17-core:

```xml
<dependency>
  <groupId>com.bucket4j</groupId>
  <artifactId>bucket4j_jdk17-core</artifactId>
  <version>8.20.0</version>
</dependency>
```

With that on the classpath, the README's quick start builds a static bucket and guards a method with it. Note that the import in the README example is io.github.bucket4j.Bucket while the Maven coordinates use the com.bucket4j groupId; copy both as given rather than assuming they match.

```java
import io.github.bucket4j.Bucket;

private static Bucket bucket = Bucket.builder()
      .addLimit(limit -> limit.capacity(20).refillGreedy(10, Duration.ofMinutes(1)))
      .build();

private void doSomethingProtected() {
   if (bucket.tryConsume(1)) {
      doSomething();
   } else {
      throw new SomeRateLimitingException();
   }
}
```

What you should see: the first twenty calls succeed immediately, after which consumption depends on refill. The README's surrounding comment describes the intent as capacity 20 tokens with a refilling speed of 1 token per 6 seconds, while the code uses refillGreedy(10, Duration.ofMinutes(1)). Those two descriptions are not written identically in the README, so read the method call as the authoritative form and treat the prose comment as an approximation. The repository also contains a bucket4j-bom module if you prefer to manage versions centrally, and mvnw and mvnw.cmd at the top level for the build.

## Where Bucket4j is the wrong tool

The clearest limitation is stated by the project itself: Bucket4j is a library, and you must write the code to achieve your goals. There is no annotation to put on a controller method, no filter registered for you, no configuration file the library reads on its own. Every protected path needs an explicit tryConsume call and an explicit decision about the failure branch. Teams that expect a drop-in middleware will find the integration cost higher than the README's five-line example suggests, because the example omits the part where you choose what a rejected request returns.

Distributed deployments carry a second cost. The bucket state moves to a remote store, so each consumption decision becomes a network round trip unless you use an asynchronous API. The README frames the async API as a way to avoid blocking application threads on network requests, and the back-end table shows that not every integration offers it: JCache and the Apache Geode integration are both marked as not supporting async, and Jedis is marked the same way. Choosing one of those back-ends for a high-throughput path is a decision you may regret.

A third boundary is the algorithm itself. Token bucket allows bursts up to the bucket capacity; if your requirement is a strictly even spacing of requests with no burst allowance, the token bucket model does not express that, and adjusting capacity and refill rate only changes how large the burst can be. The README describes no alternative algorithm.

## Bucket4j compared with Resilience4j

The comparison that comes up most often is with Resilience4j, and the difference is scope rather than quality. Resilience4j is a fault-tolerance toolkit: its rate limiter is one module among circuit breakers, retries, bulkheads and time limiters, and it is typically wired in through Spring Boot configuration and annotations. Bucket4j does one thing, the token bucket, and exposes it as a programmatic API. If your service needs a circuit breaker and a retry policy alongside a limiter, Resilience4j gives you one dependency and one configuration style. If you need a limiter whose state is shared across a cluster, Bucket4j's distributed back-end list is the more direct fit, and the README's back-end table is the evidence for that claim.

The two are not mutually exclusive in a codebase, but combining them means two configuration models and two sets of semantics for what a rejected call looks like. The practical question is whether your limit must be global across instances. Local limits expressed as annotations are cheaper to write and easier to reason about; global limits need shared state, and that is the problem Bucket4j was built around.

## Maintenance, licence and the cost of upgrading

The repository is not archived, and the last push was on 2026-09-24. Release 8.20.0 was published on 2026-09-17, following 8.19.0 on 2026-05-19 and 8.18.0 on 2026-04-12. That cadence, and the presence of a backward-compatibility-policy.md file and a backward-compatibility-tests/ module at the top level, tells you the maintainers treat API stability as something to test rather than assume.

Upgrade cost depends on which modules you use. The core dependency is a single artifact with a version number, but each distributed back-end is a separate module (bucket4j-redis, bucket4j-hazelcast-all, bucket4j-infinispan-all, bucket4j-ignite, bucket4j-jcache, bucket4j-coherence-all, bucket4j-couchbase, bucket4j-geode, and the database modules for PostgreSQL, MySQL, MariaDB, MSSQL, Oracle, DB2 and MongoDB, plus bucket4j-memcached and bucket4j-caffeine). Because the back-end capabilities differ, an upgrade that looks like a version bump can become a compatibility check against the specific store you run. The java-compatibility-matrix.md file in the repository is the place to confirm which Java versions each line supports before you plan the move.

The licence is Apache-2.0, stated in the repository and shown as the licence badge in the README. Apache-2.0 permits commercial and closed-source use and includes a patent grant. This is a description of the licence text, not legal advice; if your organisation has specific obligations around attribution or notices, have counsel read LICENSE.txt.

## Conclusion

Adopt Bucket4j when you need a token-bucket limiter inside Java code you control, especially when the same limit must hold across several JVMs and you already run Hazelcast, Infinispan, Redis or a supported database. Do not adopt it if you want declarative limits configured in YAML with no code: that is what the third-party Spring Boot starter is for, and Bucket4j's own README states plainly that it is a library, not a framework. Before committing, verify two things against your own stack: which back-end module matches your infrastructure (async support, per-entry expiration and thin-client support differ per back-end), and whether the distributed configuration you pick supports changing the bucket configuration on the fly.

## FAQ

### What is Bucket4j?

Bucket4j is a Java rate-limiting library based on the token-bucket algorithm, distributed through Maven Central and licensed under Apache-2.0. The README describes it as a library rather than a framework, so you write the code that consumes tokens and handles rejection.

### What is bucket4j-core?

The core module holds the bucket API and the token-bucket implementation. For Java 17 and later the README gives the artifact as bucket4j_jdk17-core under the com.bucket4j groupId; the repository's top-level directory is named bucket4j-core.

### Is Bucket4j free?

The repository is licensed under Apache-2.0 and the README carries the licence badge, so the library is free to use under that licence. Apache-2.0 allows commercial use; check LICENSE.txt for the exact terms.

### Is Bucket4j thread safe?

The README states that Bucket4j scales well in multi-threaded cases because it uses a lock-free implementation by default, and that other concurrency strategies can be selected when the default is not desired. It does not describe a thread-safety guarantee in any other wording.

### Is Bucket4j deprecated?

The repository is not archived and the last push was on 2026-09-24, with release 8.20.0 published on 2026-09-17. Nothing in the README or the release list indicates deprecation.

### How do I use Bucket4j to limit a method?

Build a bucket with Bucket.builder().addLimit(...).build(), then call tryConsume(1) on each protected operation and branch on the boolean result, as the README's quick start shows. The README's example uses capacity 20 with refillGreedy(10, Duration.ofMinutes(1)).

## Sources

- [bucket4j/bucket4j on GitHub](https://github.com/bucket4j/bucket4j)
- [License: Apache-2.0](https://github.com/bucket4j/bucket4j/blob/master/LICENSE)
- [Project website](https://bucket4j.com)
- [README](https://github.com/bucket4j/bucket4j/blob/master/README.md)
- [Releases](https://github.com/bucket4j/bucket4j/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/bucket4j-bucket4j
