Bucket4j: Overview and Getting Started

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

Bucket4j is a Java library that limits how fast something may happen. A bucket holds tokens, every operation takes some of them, and a refill puts them back at a fixed speed. When the bucket is empty the operation is refused, or it waits. That is the whole model, and it is the token-bucket algorithm that the next page explains in detail. This page introduces the library, lists what it ships, and builds a first working limiter for the Bookshelf API.

It was written against Bucket4j 8.21.0 (released on 2 October 2026), Spring Boot 4.1.x, Spring Framework 7.0.x and Java 21, and verified on 4 October 2026. The bucket4j-spring-boot-starter that appears in the ecosystem section is a community project; the version it needs is stated where it is used.

What Bucket4j is

Bucket4j describes itself as a library, not a framework: it has no container, no annotations of its own and no configuration files. You add a jar, build a Bucket object with a fluent builder, and call tryConsume before the work you want to limit. Everything else, such as where requests come from, what a rejection looks like and where to keep the buckets, is your code. The library does four things well:

  • A precise, allocation-light algorithm. A bucket keeps a few long values. The reference says it takes no more than about 40 bytes however many requests arrive, and the arithmetic is integer-only, so there is no floating-point drift.

  • Local and distributed buckets with the same API. A local bucket lives in the JVM. A distributed bucket lives in Redis, a database or a data grid and is reached through a ProxyManager, so that every replica of a service sees the same tokens. Application code does not change between the two.

  • Several limits per bucket. One bucket can enforce an hourly quota and a per-second burst guard at once; see token bucket and bandwidths.

  • Many consumption styles. Non-blocking (tryConsume), blocking (asBlocking()), scheduled (asScheduler()), probing (tryConsumeAndReturnRemaining) and diagnostic (asVerbose()); see the Bucket API.

It is a rate limiter, not a resilience toolkit. It does not retry, break circuits or isolate threads; Resilience4j does that, and the section explains how the two fit together on Resilience4j vs Bucket4j.

When to use it

  • API quotas by plan. The free plan of the Bookshelf API gets 20 requests per minute per API key, a pro plan 200 and a partner plan 2000. One bucket per key, with a configuration chosen by plan.

  • Abuse and DoS mitigation. Per-IP or per-account buckets on login, password reset and search endpoints.

  • A shared third-party quota. The ISBN-metadata provider allows a fixed number of calls per day for the whole company. A bucket in Redis, shared by every replica, keeps the sum under that number, which an in-process limiter cannot do.

  • Notification throttling. At most so many e-mails or push messages per user per hour.

  • Billing plans and fair use. Heavy operations cost more tokens: a search costs five tokens, a lookup costs one.

When the limit must hold per JVM only, and the only goal is to protect one call from overload, the Resilience4j RateLimiter described in RateLimiter is enough. Bucket4j is the right tool when the limit is per key, shared between instances, or needs a real token-bucket shape.

The 8.21.0 modules

Bucket4j is published under the group id com.bucket4j; the Java package is io.github.bucket4j. Since 8.11.1 every artifact name carries the minimum Java version, bucket4j_jdk17-*. The table lists everything that 8.21.0 publishes for Java 17 and later. A distributed bucket needs the core module plus one backend module. Redis needs two: redis-common and one client module.

Artifact (bucket4j_jdk17-…​) What it provides First published

core

Bucket, Bandwidth, BucketConfiguration, the local bucket builder, the ProxyManager abstractions, listeners and the in-memory implementation. Everything below depends on it.

8.11.0

bom

A bill of materials that manages every other bucket4j_jdk17-* version

8.21.0

redis-common with lettuce, jedis, redisson, vertx or glide

Redis, using compare-and-swap through a Lua script; one client module per Redis client library

8.11.0 (glide: 8.16.0, vertx: 8.17.0)

postgresql, mysql, mariadb, oracle, mssql, db2

JDBC buckets: SELECT …​ FOR UPDATE (and advisory locks on PostgreSQL)

8.11.0 (db2: 8.14.0)

jcache, hazelcast, hazelcast-4, ignite, ignite3, infinispan, infinispan-15, coherence, coherence-21

JCache and in-memory data grids; entry processors, locks or compare-and-swap, depending on the grid

8.11.0 (ignite3, infinispan-15, coherence-21: 8.21.0)

mongo-common, mongodb-sync, mongodb-async

MongoDB, synchronous and asynchronous drivers, compare-and-swap

8.15.0

couchbase, memcached, cassandra, geode

Further compare-and-swap backends

couchbase: 8.18.0, geode: 8.20.0, memcached and cassandra: 8.21.0

caffeine

A local, size- and time-bounded cache of buckets (Bucket4jCaffeine.builderFor(…​)), useful for per-key local buckets

8.11.0

The BOM manages the core, redis-common, the five Redis clients, the JDBC modules, jcache, ignite, ignite3, infinispan, hazelcast, hazelcast-4, coherence, coherence-21, geode, caffeine, the three MongoDB modules, couchbase, memcached and cassandra. It does not manage infinispan-15, and some legacy aggregators (redis, hazelcast-all, infinispan-all) stop at 8.14.0 and are not part of 8.21.0. The backend table, with each builder entry point and its atomicity model, is on Bucket4j backends.

Artifact history

The artifact names changed twice, and search results and older blog posts still show the earlier names.

Artifact Versions on Maven Central Notes

bucket4j_jdk17-core and bucket4j_jdk17-*

8.11.0 to 8.21.0

Current. Requires Java 17 or later. 8.11.0 was the first release published under this name; 8.11.1 completed the move to JDK 17 and the rename.

bucket4j_jdk11-core and bucket4j_jdk11-*

8.11.0 to 8.15.0

Nothing newer was published, although the project’s compatibility policy still mentions JDK 11 artifacts.

bucket4j_jdk8-core and bucket4j_jdk8-*

up to 8.10.1

Java 8 builds after that are commercial only.

bucket4j-core (legacy name) and bucket4j-*

7.6.1, then 8.0.0 to 8.10.1

The names used before the rename. Replace them with bucket4j_jdk17-* when upgrading.

Adding the dependency

Import the BOM once and declare the modules without versions. Spring Boot does not manage Bucket4j, so there is no other place that pins the version.

pom.xml with the BOM and the core module
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.bucket4j</groupId>
            <artifactId>bucket4j_jdk17-bom</artifactId>
            <version>8.21.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <dependency>
        <groupId>com.bucket4j</groupId>
        <artifactId>bucket4j_jdk17-core</artifactId>
    </dependency>
</dependencies>

Source: Bucket4j reference: Adding the Bucket4j dependency. The reference shows a plain dependency with a version and does not mention the BOM, which first appeared in 8.21.0; the module list is in the bucket4j-bom module of the 8.21.0 tag.

build.gradle.kts with the BOM and the core module
dependencies {
    implementation(platform("com.bucket4j:bucket4j_jdk17-bom:8.21.0"))
    implementation("com.bucket4j:bucket4j_jdk17-core")
}

A distributed Bookshelf adds the backend modules. The Maven form for Redis with the Lettuce client, which Spring Boot already brings, is:

Backend modules for a Redis-backed bucket
<dependency>
    <groupId>com.bucket4j</groupId>
    <artifactId>bucket4j_jdk17-redis-common</artifactId>
</dependency>
<dependency>
    <groupId>com.bucket4j</groupId>
    <artifactId>bucket4j_jdk17-lettuce</artifactId>
</dependency>

A first local bucket for the Bookshelf API

The smallest useful limiter keeps one bucket per API key in a ConcurrentHashMap. This is a local limiter: every instance of the service has its own map, so a client that talks to three replicas gets three times the quota. The distributed version is the subject of distributed rate limiting.

A local per-key limiter
import io.github.bucket4j.Bucket;

import java.time.Duration;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class LocalPlanLimiter {

    private final ConcurrentMap<String, Bucket> buckets = new ConcurrentHashMap<>();

    public boolean allow(String apiKey, long requestsPerMinute) {
        Bucket bucket = buckets.computeIfAbsent(apiKey, key -> Bucket.builder()
                .addLimit(limit -> limit.capacity(requestsPerMinute)
                        .refillGreedy(requestsPerMinute, Duration.ofMinutes(1)))
                .build());
        return bucket.tryConsume(1);
    }
}

Running the class with the free plan (20 per minute) and 25 calls in a row for one key lets exactly 20 through and refuses 5; a second key starts with its own full bucket. Three details are worth reading closely:

  • capacity is the bucket size, and a new bucket starts full. A client may therefore send 20 requests at once, not 20 spread over the minute. Spreading is the job of the refill and of a second bandwidth, covered on the next page.

  • refillGreedy(20, Duration.ofMinutes(1)) adds one token every three seconds, not 20 tokens once a minute.

  • The map never forgets a key. With millions of anonymous callers it is a memory leak, which is one of the reasons the production setup keeps buckets in a bounded cache or in Redis, with an expiration.

Refusing with a hint about when to retry needs the probe variant. A rejected probe tells how long to wait:

Reading the wait time of a rejected request
Bucket bucket = Bucket.builder()
        .addLimit(limit -> limit.capacity(20).refillGreedy(20, Duration.ofMinutes(1)))
        .build();
bucket.tryConsume(20);

ConsumptionProbe probe = bucket.tryConsumeAndReturnRemaining(1);
// probe.isConsumed() is false, getNanosToWaitForRefill() is 3_000_000_000 (3 seconds)

Source: Bucket4j reference: Limiting the rate of access to a REST API. Turning that value into an HTTP 429 with Retry-After is shown in the sequence diagram on the Bucket API page.

How the modules fit together

flowchart LR core["bucket4j_jdk17-core
Bucket, Bandwidth, BucketConfiguration"] local["Local buckets
Bucket.builder()"] dist["Distributed buckets
ProxyManager, AsyncProxyManager"] caff["caffeine
bounded local cache of buckets"] redis["redis-common + lettuce / jedis / redisson / vertx / glide"] jdbc["JDBC: postgresql, mysql, mariadb, oracle, mssql, db2"] grid["Grids: jcache, hazelcast, ignite, infinispan, coherence, geode"] other["mongodb, couchbase, memcached, cassandra"] manual["Manual Spring Boot integration
filter, interceptor, WebFilter"] starter["bucket4j-spring-boot-starter
community project"] gw["Spring Cloud Gateway
Bucket4j rate limiters"] core --> local core --> dist local --> caff dist --> redis dist --> jdbc dist --> grid dist --> other local --> manual dist --> manual manual --> starter dist --> gw caff --> gw

The diagram reads left to right. The core module gives you both worlds; the backends only appear once you need a bucket that lives outside the JVM. The Spring integrations at the right are three ways to put those buckets in front of HTTP traffic: your own filter, the community starter that generates the filter from YAML, and the Spring Cloud Gateway filters, which keep their buckets in an AsyncProxyManager (typically Caffeine for a single gateway, or a Redis-backed manager for several).

The ecosystem

The Spring Boot starter. bucket4j-spring-boot-starter, with the group id com.giffing.bucket4j.spring.boot.starter, wraps Bucket4j in Spring Boot auto-configuration: you declare filters and bandwidths in application.yml and the starter builds the servlet or WebFlux filter. It is a community project maintained under the MarcGiffing GitHub account, not by the Bucket4j project or by Spring, and its release cadence and Spring Boot support are its own. Bucket4j’s README recommends it in its Spring Boot starter section. The starter has two relevant lines at the time of writing: 0.14.0 (2026-02-28, Spring Boot 4.0.3, Bucket4j 8.15.0) and the pre-release 0.20.0-RC1 (2026-09-11, Spring Boot 4.1.1, Bucket4j 8.17.0), which is published to Maven Central only and changes the module layout. Both are covered on the starter page, which also says which one the examples use. The alternative that needs no third-party starter, a filter you write yourself, is on manual Spring Boot integration.

Spring Cloud Gateway. Spring Cloud Gateway 5.0.x ships a Bucket4j rate limiter for the Web MVC server (Bucket4jFilterFunctions.rateLimit) and for the WebFlux server (an argument to RequestRateLimiter). Bucket4j is an optional dependency there: you add it yourself, and the gateway build uses 8.15.0, so import the 8.21.0 BOM to align it. See Bucket4j in Spring Cloud Gateway.

Commercial Java 8 builds. The open-source artifacts for Java 8 stop at 8.10.1. The project offers later Java 8 builds commercially; see the "Java compatibility" page linked in the references. Nothing in this section needs them.

Version 9. A 9.x line has open milestones on GitHub but is not released. Under the project’s backward-compatibility policy, deprecated API is removed only in a major release, so everything in this section that avoids deprecated API should carry over. The deprecations that exist today, and the discrepancies between the reference and the 8.21.0 code, are collected on migration and what’s changed.

A reading path

  1. This page, to know what is in the box.

  2. Token bucket and bandwidths: the algorithm and the refill styles.

  3. The Bucket API: every consumption method, listeners, time and configuration replacement.

  4. Distributed rate limiting and backends: sharing buckets across replicas.

  5. Spring Boot: manual integration, the starter and Spring Cloud Gateway.

  6. Operating it: observability, testing and the production checklist.

  7. Resilience4j vs Bucket4j to decide which tool to use where, and how to use both.