Bucket4j: Migration & What’s Changed

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 keeps its API stable inside the 8.x line, so most upgrades are mechanical, but four things have moved enough to deserve a page of their own: the bandwidth builder that replaced Bandwidth.simple and Refill in 8.5.0, the artifact names that changed to bucket4j_jdk17- in 8.11.x, the *builder entry points (Bucket4jLettuce.casBasedBuilder and its relatives) that replaced direct constructors, and the community Spring Boot starter, whose coordinates and supported modes changed between its versions 0.13.0, 0.14.0 and 0.20.0-RC1. The page ends with every place where the official documentation disagrees with the code, gathered from the whole Bucket4j part of this section so that you can see them in one table.

This is the one page of the section where deprecated API appears, and only as the "before" side of a migration. It was written against Bucket4j 8.21.0 (released on 2 October 2026), Spring Boot 4.1.x (4.1.1), Spring Framework 7.0.x and Java 21, with the community starter at 0.14.0 and 0.20.0-RC1 and Spring Cloud Gateway 5.0.3, and verified on 5 October 2026. Every Java example on this page, in its old and in its new form, was compiled and run against Bucket4j 8.21.0 in a scratch project, and a test proved that each old form behaves identically to its replacement on a manual clock. The vocabulary (capacity, refill, bandwidth) is in Token Bucket and Bandwidths; the Resilience4j counterpart of this page is Migration & What’s Changed.

Choosing a migration path

flowchart TD S["Which Bucket4j do you run?"] --> V{"7.x?"} V -- yes --> V1["7.x to 8.x
(JDK 11, groupId com.bucket4j)"] V1 --> B V -- no --> B{"8.0 to 8.4?"} B -- yes --> B1["8.5 builder
(Bucket4j class gone,
Bandwidth.builder)"] B1 --> A B -- no --> A{"Artifact named
bucket4j-core or bucket4j_jdk8-*?"} A -- yes --> A1["8.11.x rename
to bucket4j_jdk17-*
(needs Java 17)"] A1 --> D A -- no --> D{"Direct constructors or
build(key, config)?"} D -- yes --> D1["Builder entry points
and build(key, supplier)"] D1 --> ST D -- no --> ST{"Community Spring Boot
starter?"} ST -- yes --> ST1["0.12 to 0.13 to 0.14 to 0.20
(modules, no gateway filter)"] ST -- no --> DONE["Check the discrepancy table
before trusting the old docs"] ST1 --> DONE
Figure 1. Which Bucket4j migration applies to you

Do the steps in this order. The artifact rename is a build change that does not touch your code, the bandwidth builder and the entry points are code changes that compile on every 8.x from 8.5.0 on, and the starter depends on the Bucket4j version it pins, so it comes last.

timeline title Bucket4j and its Spring integrations 7.0.0 : distributed facilities redesigned 8.0.1 : JDK 11 and groupId com.bucket4j 8.5.0 : Bandwidth.builder and addLimit 8.6.0 : MathType IEEE_754 removed 8.11.1 : bucket4j_jdk17 artifacts : unified builders : Redis split 8.15.0 : last JDK 11 artifacts : MongoDB 8.21.0 : bucket4j_jdk17-bom : Memcached and Cassandra : Ignite 3 Starter 0.14.0 : Spring Boot 4 Starter 0.20.0-RC1 : new modules : no gateway filter
Figure 2. Milestones of the Bucket4j line that matter for a migration

Source: Bucket4j: previous releases, with the release notes of each version linked in the tables below, and Maven Central, bucket4j_jdk17-core for the dates of publication.

7.x to 8.x

Bucket4j 7.0.0 was the release that redesigned the distributed facilities (a ProxyManager per backend, rolling upgrades, non-JCache backends such as Redis and relational databases), and its release notes announce that the API it deprecated would be removed "in 2023". The 8.x line then changed the platform rather than the model. The notes of 8.0.1 (31 July 2022) list the migration to JDK 11 and the change of the Maven groupId to com.bucket4j; the Spring Boot starter’s own README dates the JDK 11 move to 8.0.0 and adds that the project then published dedicated Java 8 artifacts. Maven Central confirms the groupId move: the 7.x artifacts live under com.github.vladimir-bukhtoyarov, and the group com.bucket4j starts with 7.6.1 and 8.0.0.

Maven coordinates, 7.x to 8.0 to 8.10
<!-- Bucket4j 7.6.0 (JDK 8 baseline) -->
<dependency>
    <groupId>com.github.vladimir-bukhtoyarov</groupId>
    <artifactId>bucket4j-core</artifactId>
    <version>7.6.0</version>
</dependency>

<!-- Bucket4j 8.0.0 to 8.10.1: new groupId, JDK 11 baseline -->
<dependency>
    <groupId>com.bucket4j</groupId>
    <artifactId>bucket4j-core</artifactId>
    <version>8.10.1</version>
</dependency>

<!-- the same releases built for Java 8 -->
<dependency>
    <groupId>com.bucket4j</groupId>
    <artifactId>bucket4j_jdk8-core</artifactId>
    <version>8.10.1</version>
</dependency>

Source: Bucket4j 8.0.1 release notes and Bucket4j 7.0.0 release notes. The artifact versions were read from maven-metadata.xml on Maven Central on 5 October 2026.

Inside 8.x the code that most often needs touching is the entry point of a local bucket. The utility class Bucket4j was already @Deprecated in 7.6.0 and 8.4.0 (its methods delegate to the replacements) and is absent from 8.5.0, so code that still calls it stops compiling on 8.21.0.

Table 1. The removed Bucket4j factory class
Removed (7.x to 8.4.0) Use instead (since 7.x, the only form from 8.5.0)

Bucket4j.builder()

Bucket.builder()

Bucket4j.configurationBuilder()

BucketConfiguration.builder()

The 8.21.0 source has no Bucket4j class; a test in the scratch project confirmed that Class.forName("io.github.bucket4j.Bucket4j") throws ClassNotFoundException on 8.21.0. The changes that need no code, but that an upgrade across 8.x can meet, are in the release notes. This table lists the ones with an effect on how a service is built or run.

Table 2. What the 8.x release notes announce (8.0 to 8.16)
Release Date Change that can affect a migration

8.0.1

31 July 2022

JDK 11 baseline, groupId com.bucket4j, fix of the Verbose API.

8.2.0

5 March 2023

Redis Cluster support. Support for Spring Data Redis stopped: switch to the Lettuce, Jedis or Redisson integrations. JDBC integrations accept non-integer primary keys.

8.3.0

2 May 2023

Key mapping on the ProxyManager (withMapper), contravariant arguments for CaffeineProxyManager. Update Redisson to a recent version.

8.5.0

2 October 2023

The builder API ("closer to the token-bucket description", #401). The next section.

8.6.0

3 November 2023

The experimental MathType.IEEE_754 is gone (see below). The notes list only the fix of a negative tryConsumeAsMuchAsPossible result.

8.7.0

12 November 2023

Microsoft SQL Server, UnifiedJedis. Breaking: support for Amazon DynamoDB was removed.

8.9.0

24 February 2024

Request timeouts (requestTimeout) where the backend supports them (Redis, JDBC).

8.10.1

March 2024

Breaking: Hazelcast 3.x is no longer supported; the minimum is 4.1 since 8.10.0.

8.11.1

24 April 2024

JDK 17, new artifact names, the Redis split and unified builders. Two sections below.

8.12.1

1 May 2024

Cleaner of expired buckets for the relational databases (removeExpired), flexible per-entry expiration for Ignite, Infinispan and Coherence, Verbose API for the blocking and scheduled buckets.

8.13.1

7 June 2024

Fix of the batching optimization.

8.14.0

18 August 2024

DB2.

8.15.0

24 August 2025

MongoDB. The last release with bucket4j_jdk11-* artifacts.

8.16.1

2 February 2026 (8.16.0 on 28 December 2025)

Valkey GLIDE, a retry strategy for compare-and-swap, OSGi metadata.

Source: the previous-releases page with the release notes of each version, and the GitHub releases for the exact dates. A release whose date is given only as a month had no GitHub release; the month comes from Maven Central. 8.11.0 is the first version on Maven Central under the jdk17 names, and its content is described by the 8.11.1 notes.

The 8.5.0 bandwidth builder

Until 8.4.0 a bandwidth was created with a static factory: Bandwidth.simple(capacity, period) for a bucket that refills completely every period, and Bandwidth.classic(capacity, Refill…​) for everything else, with withInitialTokens and withId as wither methods on the result. The factories are named after implementation details ("classic", "simple") rather than after the two numbers of the token-bucket algorithm, and which Refill was greedy or intervally was a question of memorising factory names. Release 8.5.0 (issue #401, "making the builder API to be more close with token-bucket description") added a staged builder: Bandwidth.builder() then capacity, then one of four refill methods, then the optional id and initialTokens, then build(). On a BucketConfiguration or Bucket builder, addLimit(limit → …​) takes the same stages as a lambda. The old factories, the withInitialTokens and withId methods and the whole Refill class were marked @Deprecated in 8.5.0; they still work in 8.21.0, and under the project’s backward-compatibility policy deprecated API is removed only in a major release, so not before 9.0.0. Version 9 has open milestones on GitHub and is not released.

Table 3. From the deprecated API to the builder (all of it still compiles on 8.21.0)
Before 8.5.0 (deprecated) 8.5.0 and later

Bandwidth.simple(100, Duration.ofMinutes(1))

Bandwidth.builder().capacity(100).refillGreedy(100, Duration.ofMinutes(1)).build()

Bandwidth.classic(100, Refill.greedy(10, Duration.ofMinutes(1)))

Bandwidth.builder().capacity(100).refillGreedy(10, Duration.ofMinutes(1)).build()

Refill.of(…​), Refill.smooth(…​) (aliases of Refill.greedy)

refillGreedy(tokens, period)

Bandwidth.classic(100, Refill.intervally(10, Duration.ofMinutes(1)))

Bandwidth.builder().capacity(100).refillIntervally(10, Duration.ofMinutes(1)).build()

Refill.intervallyAligned(tokens, period, first, false)

refillIntervallyAligned(tokens, period, first)

Refill.intervallyAligned(tokens, period, first, true)

refillIntervallyAlignedWithAdaptiveInitialTokens(tokens, period, first)

bandwidth.withInitialTokens(20)

…​initialTokens(20) before build()

bandwidth.withId("plan")

…​id("plan") before build()

Bucket.builder().addLimit(bandwidth) with a prebuilt Bandwidth

Still valid; or addLimit(limit → limit.capacity(…​).refillGreedy(…​))

The same mapping appears, next to the algorithm it describes, on Token Bucket and Bandwidths. The code below shows the two forms side by side. The first method is the "before"; it still compiles, with deprecation warnings.

Before: deprecated factories, Refill and the withers (compiles on 8.21.0 with warnings)
Instant firstRefill = Instant.parse("2026-01-01T00:00:00Z");

Bandwidth[] before = {
    Bandwidth.simple(100, Duration.ofMinutes(1)),
    Bandwidth.classic(100, Refill.greedy(10, Duration.ofMinutes(1))),
    Bandwidth.classic(100, Refill.intervally(10, Duration.ofMinutes(1))),
    Bandwidth.classic(100, Refill.intervallyAligned(10, Duration.ofMinutes(1), firstRefill, false)),
    Bandwidth.classic(100, Refill.intervallyAligned(10, Duration.ofMinutes(1), firstRefill, true)),
    Bandwidth.classic(100, Refill.of(10, Duration.ofMinutes(1))).withInitialTokens(20).withId("plan")
};

Bucket bucket = Bucket.builder()
    .addLimit(Bandwidth.classic(10, Refill.greedy(10, Duration.ofMinutes(1))))
    .build();
After: the staged builder (Bucket4j 8.5.0 and later)
Instant firstRefill = Instant.parse("2026-01-01T00:00:00Z");

Bandwidth[] after = {
    Bandwidth.builder().capacity(100).refillGreedy(100, Duration.ofMinutes(1)).build(),
    Bandwidth.builder().capacity(100).refillGreedy(10, Duration.ofMinutes(1)).build(),
    Bandwidth.builder().capacity(100).refillIntervally(10, Duration.ofMinutes(1)).build(),
    Bandwidth.builder().capacity(100).refillIntervallyAligned(10, Duration.ofMinutes(1), firstRefill).build(),
    Bandwidth.builder().capacity(100)
        .refillIntervallyAlignedWithAdaptiveInitialTokens(10, Duration.ofMinutes(1), firstRefill).build(),
    Bandwidth.builder().capacity(100).refillGreedy(10, Duration.ofMinutes(1)).initialTokens(20).id("plan").build()
};

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

The test that accompanies this page asserted that each of the six before bandwidths is equals to the corresponding after one, field by field (capacity, refill tokens and period, greedy or intervally, aligned time, adaptive initial tokens, initial tokens and id), and that the two buckets give the same answers on a manual clock: with a capacity of 10 refilled 10 per minute, the exhausted bucket reported exactly 6 seconds to wait, 5 tokens were back after 30 seconds, and 10 after five minutes, for both. The intervally variant gave 0 tokens after 59 seconds and 10 after 60, for both.

Source: Bucket4j reference: Bandwidth, Bucket4j reference: Refill types and the 8.21.0 source of Bandwidth and Refill, whose @Deprecated tags are where the migration is announced. The 8.21.0 reference no longer shows the old factories at all (a search of its page and of its AsciiDoc sources found no Bandwidth.simple, Bandwidth.classic or Refill.), so the deprecation is visible only in the Javadoc and in your compiler’s warnings.

Two details of the builder are worth knowing before you convert code in bulk. initialTokens cannot be combined with refillIntervallyAlignedWithAdaptiveInitialTokens (the builder throws), exactly as withInitialTokens could not be combined with an adaptive Refill. And withNanosecondPrecision() belongs to the local bucket builder, not to the Bandwidth builder; the aligned refills need a wall-clock time meter and refuse the nanosecond one.

Ids change what a replacement means

The builder makes it easy to add id("…​") while you are converting. Do not add ids to an existing live configuration on the way: replaceConfiguration with TokensInheritanceStrategy.PROPORTIONALLY matches the old and the new bandwidth by id (or by position when neither has one). A run in the scratch project replaced capacity(20) (no id) by capacity(40)…​id("plan") and the bucket ended with 40 tokens, not the 30 that proportional inheritance gives, because the new bandwidth had an id the old one lacked and was treated as a different bandwidth. With both ids unset the same replacement gave 30. See Bandwidth ids decide which old bandwidth is which.

MathType.IEEE_754 was removed in 8.6

Before 8.6 the MathType enum had a second, experimental value, IEEE_754, intended for testing backends written in Lua or JavaScript that lack 64-bit integer arithmetic. The 8.5.0 source still has it and the 8.6.0 source does not (commit "#410 remove experimental code", 24 October 2023, which the 8.6.0 tag contains). The 8.6.0 release notes do not mention it. On 8.21.0 MathType is @Experimental and has one value, INTEGER_64_BITS, which a test of the scratch project asserted. Code that referred to MathType.IEEE_754 has to be deleted; there is no replacement, and nothing in the 8.21.0 reference documents MathType.

The distributed side: build(key, supplier) and the builder entry points

A ProxyManager hands out a BucketProxy for a key. The older way to pass the configuration was the BucketConfiguration itself: proxyManager.builder().build(key, configuration). In 8.21.0 that overload is @Deprecated ("use build(Object, Supplier) instead. This method will be removed soon" in the source). The replacement takes a Supplier<BucketConfiguration> that the manager calls only when the bucket does not exist yet, which saves building a configuration on every request. The other change is how the managers are created. The classes with public constructors (CaffeineProxyManager, InfinispanProxyManager and the JDBC SQLProxyConfiguration family) are deprecated in favour of entry-point classes named Bucket4j<Backend>, whose static methods return a builder that carries the client-side settings (clientClock, expirationAfterWrite, requestTimeout, maxRetries, retryStrategy, executionStrategy and the others). The 8.11.1 release notes list "unified style builders for all ProxyManagers" (#467).

The example uses the Caffeine backend because it needs no server: the old constructor keeps the buckets in a local cache, and its keepAfterRefillDuration argument means "keep an entry for that long after the bucket has refilled completely", which is ExpirationAfterWriteStrategy.basedOnTimeForRefillingBucketUpToMax(duration) in the new API.

Before: public constructor and build(key, config) (deprecated, compiles on 8.21.0 with warnings)
ProxyManager<String> proxyManager = new CaffeineProxyManager<>(
    Caffeine.newBuilder().maximumSize(10_000),
    Duration.ofMinutes(1),                                   // keep after refill
    ClientSideConfig.getDefault().withClientClock(clock));

BucketConfiguration configuration = BucketConfiguration.builder()
    .addLimit(Bandwidth.simple(100, Duration.ofMinutes(1)))
    .build();

BucketProxy bucket = proxyManager.builder().build("client-1", configuration);
After: Bucket4jCaffeine.builderFor and build(key, supplier)
ProxyManager<String> proxyManager = Bucket4jCaffeine
    .<String>builderFor(Caffeine.newBuilder().maximumSize(10_000))
    .expirationAfterWrite(ExpirationAfterWriteStrategy
        .basedOnTimeForRefillingBucketUpToMax(Duration.ofMinutes(1)))
    .clientClock(clock)
    .build();

BucketConfiguration configuration = BucketConfiguration.builder()
    .addLimit(limit -> limit.capacity(100).refillGreedy(100, Duration.ofMinutes(1)))
    .build();

BucketProxy bucket = proxyManager.builder().build("client-1", () -> configuration);
// or, when the supplier is all you need:
BucketProxy same = proxyManager.getProxy("client-1", () -> configuration);

In the test both managers were given a manual TimeMeter as clock. After consuming 70 of 100 tokens both proxies reported 30 available, and after 30 simulated seconds both reported 80. getProxy(key, supplier) returned a proxy over the same bucket as builder().build(key, supplier): a second proxy for the same key was refused a token that the first had taken. The constructors are @Deprecated in the 8.21.0 source (a reflection check in the test confirmed it for the two-argument CaffeineProxyManager constructor and for RemoteBucketBuilder.build(Object, BucketConfiguration)).

Source: Bucket4j reference: ProxyManager and the 8.21.0 source of Bucket4jCaffeine, RemoteBucketBuilder and AbstractProxyManagerBuilder. The backend-by-backend entry points and what each requires are on Bucket4j backends, and the settings of the builder on distributed rate limiting.

8.11.1: JDK 17 and the artifact rename

The 8.11.1 release notes list three items: split Redis support into individual Maven modules (#466), unified builders (#467) and the migration to JDK 17 (#474). Under "Backward incompatible changes" they say that the target JDK is now part of the artifact name: com.bucket4j:bucket4j-core became com.bucket4j:bucket4j_jdk17-core, and that the Redis dependencies were split into the Jedis, Lettuce and Redisson modules. On Maven Central bucket4j_jdk17- starts at 8.11.0 (the GitHub release 8.11.0 is dated 24 April 2024) and the notes are filed under 8.11.1. Because the *artifactId changes, a dependency bot that follows bucket4j-core never sees the new releases: the legacy bucket4j-core ends at 8.10.1 on Maven Central.

Table 4. The four artifact families
Artifact ids Versions on Maven Central Notes

bucket4j_jdk17-*

8.11.0 to 8.21.0

Current line. Java 17 or later.

bucket4j_jdk11-*

8.11.0 to 8.15.0

Last built in August 2025. Nothing newer exists, although the project’s compatibility policy still promises JDK 11 artifacts.

bucket4j_jdk8-*

8.0.0 to 8.10.1

Last published in March 2024. Later Java 8 builds are commercial only.

bucket4j-* (legacy)

7.6.1, 8.0.0 to 8.10.1

The names used before the rename; the Redis integration is a single bucket4j-redis.

Before: Bucket4j 8.10.1 on a Java 11 baseline
<dependency>
    <groupId>com.bucket4j</groupId>
    <artifactId>bucket4j-core</artifactId>
    <version>8.10.1</version>
</dependency>
<dependency>
    <groupId>com.bucket4j</groupId>
    <artifactId>bucket4j-redis</artifactId>
    <version>8.10.1</version>
</dependency>
After: Bucket4j 8.21.0, Java 17 or later, versions from the BOM
<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>
    <dependency>
        <groupId>com.bucket4j</groupId>
        <artifactId>bucket4j_jdk17-redis-common</artifactId>
    </dependency>
    <dependency>
        <groupId>com.bucket4j</groupId>
        <artifactId>bucket4j_jdk17-lettuce</artifactId>
    </dependency>
</dependencies>

The Redis rule is that one redis-common and exactly one client module (lettuce, jedis, redisson, and from 8.16 and 8.17 glide and vertx) replace the single legacy artifact. The aggregator bucket4j_jdk17-redis stopped at 8.14.0 and is not a dependency to use. If your runtime is Java 11, 8.15.0 is the newest release you can run, with the bucket4j_jdk11- ids, and the Java 8 route is the commercial one. The project’s java-compatibility-matrix.md still shows the legacy names for JDK 11 (bucket4j-core) and bucket4j_jdk8- for Java 8, so it predates the rename; the table above is the Maven Central truth.

Source: Bucket4j 8.11.1 release notes, Bucket4j reference: Adding the Bucket4j dependency and the bucket4j-bom module at 8.21.0. The artifact table was built from the maven-metadata.xml files of Maven Central. The Maven history is also on the overview page.

From 8.16 to 8.21: new entry points and modules

Since 8.16 the releases have added backends and one build tool, not new concepts. Every new module follows the entry-point rule of the previous section: a Bucket4j<Backend> class with static builder methods, no public constructor to learn. The versions below are the first on Maven Central, cross-checked against the release notes.

Table 5. Entry points and modules added in 8.16 to 8.21 (8.21.0 signatures)
Release Module (artifact bucket4j_jdk17-…​) Entry point and notes

8.16.x

glide (Valkey GLIDE)

Bucket4jGlide.casBasedBuilder(BaseClient). The 8.16.1 notes also list a retry strategy for compare-and-swap (#560) and OSGi metadata (#561).

8.17.0

vertx (Redis through Vert.x)

Bucket4jVertx.casBasedBuilder(Redis), key type byte[]; a fix for an Infinispan end-of-file error on a missing key.

8.18.0

couchbase

Bucket4jCouchbase.compareAndSwapBasedBuilder(Collection) or (AsyncCollection), key type String, with Mapper<K> overloads.

8.19.0

core

The compare-and-swap retry mechanism (#580). In 8.21.0 the builders expose it as maxRetries and retryStrategy (with RetryStrategies.exponentialBackoffWithJitter); with neither set the loop retries indefinitely.

8.20.0

geode (Gemfire)

Bucket4jGeode.compareAndSwapBasedBuilder(Region) and functionBasedBuilder(Region). Redis Sentinel through Lettuce (#588); stack-trace generation disabled in BucketExecutionException (#595); a fix of MySQL gap-lock deadlocks (#606).

8.21.0

memcached

Bucket4jMemcached.casBasedBuilder(MemcacheClient<byte[]>), key type String, with a mapper overload.

8.21.0

cassandra

Bucket4jCassandra.compareAndSwapBasedBuilder(CqlSession), key type String, with a mapper overload.

8.21.0

ignite3

Bucket4jIgnite3.builder(). A different product from ignite; it needs a table that you create.

8.21.0

infinispan-15, coherence-21

Dedicated modules for the older majors: infinispan-15 is built against Infinispan 15.0.4.Final while the plain infinispan module is built against 16.2.2, and coherence-21 against Coherence 21.06 while coherence follows 26.07. The entry points are those of the plain modules.

8.21.0

bom

bucket4j_jdk17-bom manages every module above except infinispan-15, the aggregators (redis, hazelcast-all, infinispan-all) and jdk17. Declare an explicit version for infinispan-15.

8.21.0

core

Serialization moved from byte-array streams to ByteBuffer (#625) and reset() skips a needless token recalculation (#634).

Two facts from this list matter at upgrade time. The redis, hazelcast-all and infinispan-all aggregator artifacts have no 8.21.0 build, so a dependency on one of them freezes at 8.14.0; use the individual modules. And the release notes of 8.20.0 and 8.21.0 repeat the items of earlier releases (#580, #588, #615, #595 and #606 appear in both), so read them as a running list and not as a per-release diff; the Maven Central versions are the reliable record of which module arrived when.

The following code shows what a new-style entry point looks like in a service and compiles on 8.21.0 with only the Caffeine module on the classpath; the other entry points above have the same shape and differ in the client object they take.

An 8.21.0 proxy manager and a bucket through the supplier form
ProxyManager<String> proxyManager = Bucket4jCaffeine
    .<String>builderFor(Caffeine.newBuilder().maximumSize(10_000))
    .expirationAfterWrite(ExpirationAfterWriteStrategy
        .basedOnTimeForRefillingBucketUpToMax(Duration.ofMinutes(5)))
    .build();

Supplier<BucketConfiguration> freePlan = () -> BucketConfiguration.builder()
    .addLimit(limit -> limit.capacity(20).refillGreedy(20, Duration.ofMinutes(1)))
    .build();

BucketProxy bucket = proxyManager.getProxy("api:alice", freePlan);
boolean allowed = bucket.tryConsume(1);

The community starter: 0.12, 0.13, 0.14 and 0.20

The Spring Boot starter described on its own page is a community project and has its own release line. Its README carries a migration guide, which this section reads together with the poms and sources of the tags 0.13.0, 0.14.0 and 0.20.0-RC1. The facts that matter for a migration:

Table 6. Starter versions and what each one moved
Starter Spring Boot Bucket4j Layout What changed

0.12.x

3.2 to 3.4

8.9.0 to 8.10.x

one starter

The removed rate-limits[].expression property is replaced by cache-key. Three metric counters are added by default (parked, interrupted, delayed). Bucket4j artifacts are still the legacy ids.

0.13.0

3.5.0

8.14.0

bucket4j-spring-boot-starter and -context

Bucket4j dependencies carry jdk17 in the artifact id (the 8.11.x rename).

0.14.0

4.0.3

8.15.0

bucket4j-spring-boot-starter and -context

Spring Boot 4. The README says "Spring Gateway Support dropped" here, but the 0.14.0 source still has FilterMethod.GATEWAY and the Gateway filter factory.

0.20.0-RC1

4.1.1

8.17.0

core, context, starter-autoconfigure, starter-aop, starter-servlet, starter-webflux and one module per cache

Restructured modules with explicit dependencies per use case. FilterMethod.GATEWAY and the Gateway filter are removed. A new cache-postgresql module.

No 0.20.0 GA exists on Maven Central: bucket4j-spring-boot-starter stops at 0.14.0, the new artifacts list 0.20.0-RC1 as their only version, and the GitHub releases page marks 0.14.0 as the latest. On Spring Boot 4.1 the starter is therefore a release candidate, on Spring Boot 4.0 it is 0.14.0 and on Spring Boot 3.5 it is 0.13.0; check again before you adopt, because the coordinates of a GA may differ from the RC. The README of 0.20.0-RC1 contradicts the sources on two points that matter here: its version list says "0.14.0 - Spring Gateway Support dropped" (the Gateway filter went in 0.20.0-RC1) and "0.20.0 - Bucket4j 8.16.x - Spring Boot 4.0.0" (the pom has Bucket4j 8.17.0 and Spring Boot 4.1.1).

Dependencies: from one starter to feature and cache modules

With 0.14.0 an application declared the starter and the third-party library of its cache; the starter brought Bucket4j core and the JCache module itself. With 0.20.0-RC1 the application picks a feature module (starter-servlet, starter-webflux, starter-aop) and a cache module, in the group com.giffing.bucket4j.spring.boot.starter. starter-servlet and starter-webflux cannot be on the same classpath. The starter pins Bucket4j 8.17.0, so import the Bucket4j BOM to move every jar to 8.21.0.

Before: starter 0.14.0 on Spring Boot 4.0.x with a servlet filter and JCache
<dependency>
    <groupId>com.giffing.bucket4j.spring.boot.starter</groupId>
    <artifactId>bucket4j-spring-boot-starter</artifactId>
    <version>0.14.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>jcache</artifactId>
    <version>3.2.4</version>
</dependency>
After: starter 0.20.0-RC1 on Spring Boot 4.1.x with the same behaviour
<dependencyManagement>
    <dependencies>
        <!-- the starter pins Bucket4j 8.17.0; this moves every Bucket4j jar to 8.21.0 -->
        <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.giffing.bucket4j.spring.boot.starter</groupId>
        <artifactId>starter-servlet</artifactId>
        <version>0.20.0-RC1</version>
    </dependency>
    <dependency>
        <groupId>com.giffing.bucket4j.spring.boot.starter</groupId>
        <artifactId>cache-jcache</artifactId>
        <version>0.20.0-RC1</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    <dependency>
        <groupId>com.github.ben-manes.caffeine</groupId>
        <artifactId>jcache</artifactId>
        <version>3.2.4</version>
    </dependency>
</dependencies>

Method-level limits add starter-aop and, on Spring Boot 4, spring-boot-starter-aspectj: the spring-boot-starter-aop that the README names does not exist in Boot 4. The module list, the cache-to-use value of each cache module and the beans each one needs are in the starter page.

Source: starter README: Migration guide and the 0.20.0-RC1 tag, whose pom.xml files give the module list, with the poms of the tags 0.13.0 and 0.14.0 for the earlier layout and versions.

YAML: what changes and what does not

The property model is stable across the three Boot-4-era versions. The same filter YAML bound and limited identically on 0.14.0 (Spring Boot 4.0.3) and on 0.20.0-RC1 (Spring Boot 4.1.1) in the scratch project of this section, and the property classes of the two tags have the same keys and defaults for bucket4j.filters[], rate-limits[] and bandwidths[]. What changes is a mode that disappears and one property that was removed earlier.

Before: a 0.14.0 Gateway filter (the only filter-method value that disappears)
bucket4j:
  enabled: true
  filters:
    - id: gateway-books
      cache-name: buckets
      filter-method: gateway          # FilterMethod.GATEWAY exists in 0.13.0 and 0.14.0
      url: /api/.*
      rate-limits:
        - cache-key: "getRemoteAddr()"
          bandwidths:
            - capacity: 100
              time: 1
              unit: minutes
After: the servlet filter on 0.20.0-RC1 (the same keys, a supported filter-method)
bucket4j:
  enabled: true
  cache-to-use: jcache
  filters:
    - id: servlet-books
      cache-name: buckets
      filter-method: servlet          # SERVLET, WEBFLUX and JMS are the values left
      url: /api/.*
      rate-limits:
        - cache-key: "getRemoteAddr()"
          bandwidths:
            - capacity: 100
              time: 1
              unit: minutes

On a Spring Cloud Gateway application there is no replacement inside the starter: filter-method: gateway no longer binds on the 0.20.0-RC1 tag and the Gateway filter factory is gone. The supported route is the gateway’s own limiter, which uses Bucket4j directly, described on Bucket4j in Spring Cloud Gateway. And the removed expression property, which the README’s 0.12 entry names, is a plain rename:

Before and after: the 0.12 rename of expression to cache-key
# before 0.12 (the property is removed, not just deprecated)
bucket4j:
  filters:
    - cache-name: buckets
      url: /api/.*
      rate-limits:
        - expression: "getRemoteAddr()"
          bandwidths:
            - capacity: 100
              time: 1
              unit: minutes

# 0.12 and later
bucket4j:
  filters:
    - cache-name: buckets
      url: /api/.*
      rate-limits:
        - cache-key: "getRemoteAddr()"
          bandwidths:
            - capacity: 100
              time: 1
              unit: minutes

All key names in these three YAML blocks were checked against the property classes of the 0.20.0-RC1 tag (Bucket4JConfiguration, RateLimit, BandWidth) and, for filter-method: gateway, against the FilterMethod enum of 0.14.0, which has SERVLET, WEBFLUX, GATEWAY and JMS, where the 0.20.0-RC1 enum has no GATEWAY. A neighbouring expression exists only on the metric tags (MetricTag), a different property. The starter also needs a Bean Validation provider (spring-boot-starter-validation), because it validates its properties at startup.

Source: starter README: Bucket4j complete properties and the FilterMethod, Bucket4JConfiguration and RateLimit classes of the 0.20.0-RC1 tag.

The full discrepancy table

Official documentation drifts from code. This is the list of places where the Bucket4j reference, the Javadoc, the project README and release notes, the community starter’s README and the Spring Cloud Gateway reference disagree with what the code does, checked against Bucket4j 8.21.0, the starter tags and Spring Cloud Gateway 5.0.3 on 4 and 5 October 2026. Each row names the page of this section where the topic is explained with a working example. Where the reference is stale, the row links the the 8.21.0 source file or the release notes that show the real behaviour; the links to the reference itself are in the page that the last column names.

Table 7. Where the documentation disagrees with the code
Topic Official docs say 8.21.0 (starter, gateway) behaviour Covered on

Bucket4j: deprecated Bandwidth.simple, Bandwidth.classic, Refill.*

The 8.21.0 reference no longer shows them (zero occurrences in its page and AsciiDoc sources). The 8.5.0 release notes say only "builder API closer to the token-bucket description" and do not list the deprecations.

Deprecated since 8.5.0 in the source (Bandwidth, Refill); still work, removal not before 9.0.0. Use Bandwidth.builder().

Token bucket, this page

Bucket4j: Bandwidth.builder(limit → …​)

Shown once in the reference (technical limitations, the refill-period example) and in the Javadoc of BandwidthBuilder.

There is no such overload: only the no-argument Bandwidth.builder() exists. The lambda form is addLimit(limit → …​) on the configuration and bucket builders.

Bucket API, Token bucket

Bucket4j: custom TimeMeter example (quick start)

ClusteredTimeMeter implements only currentTimeNanos().

TimeMeter has two abstract methods, so the example does not compile: isWallClockBased() is also required.

Bucket API, Testing

Bucket4j: withClientSideConfig(…​) (client-side configuration, Redis examples)

Shown on Bucket4jLettuce.casBasedBuilder and the other backend builders.

Does not exist on the 8.21.0 builders (it survives on the deprecated SQLProxyConfigurationBuilder). Client-side settings are separate methods of AbstractProxyManagerBuilder: requestTimeout, maxRetries, retryStrategy, clientClock, executionStrategy, expirationAfterWrite, backwardCompatibleWith, defaultListener, defaultRecoveryStrategy.

Distributed rate limiting, Backends, Production checklist

Bucket4j: build(key, configuration)

The Javadoc of the supplier overload says it has "the same semantic" as build(Object, BucketConfiguration).

The BucketConfiguration overload is @Deprecated in RemoteBucketBuilder ("will be removed soon"); use build(key, supplier) or getProxy.

Distributed rate limiting, this page

Bucket4j: Infinispan Hot Rod example

The variable is typed InfinispanProxyManager<K>.

Bucket4jInfinispan.hotrodClientBasedBuilder(RemoteCache) builds a HotrodInfinispanProxyManager<K> (Bucket4jInfinispan).

Backends

Bucket4j: asynchronous servlet example

new InfinispanProxyManager(bucketMap).asAsync(), with javax.servlet.http.HttpServlet.

The constructors are @Deprecated (InfinispanProxyManager); use the Bucket4jInfinispan builders. On Spring Boot 4 the package is jakarta.servlet.

Distributed rate limiting

Bucket4j: REST filter and JCache servlet examples

javax.servlet.Filter, javax.servlet.http.HttpServlet.

Spring Boot 4 (Jakarta EE 11) uses jakarta.servlet; the javax examples do not compile there.

Bucket API, Manual integration

Bucket4j: Bucket.consume()

Older descriptions and examples imply a blocking consume.

Bucket has no consume method. Blocking consumption is asBlocking().consume(…​); asynchronous waiting is asScheduler().tryConsume(n, timeout, executor).

Bucket API

Bucket4j: proxy-manager optimizations

Not in the reference.

Optimizations.batching(), delaying(…​), predicting(…​), SkipSyncOnZeroOptimization and ManuallySyncingOptimization exist and are described only in the Javadoc of Optimizations.

Distributed rate limiting

Bucket4j: Caffeine module

The reference mentions Caffeine nowhere; the README links the bucket4j-caffeine module at the stale 8.12 tag.

Bucket4jCaffeine.builderFor(Caffeine) exists in bucket4j_jdk17-caffeine (Bucket4jCaffeine); both CaffeineProxyManager constructors are deprecated.

Backends, this page

Bucket4j: MathType, SynchronizationStrategy

Not documented in the reference.

MathType is @Experimental with one value (MathType; IEEE_754 was removed in 8.6). LOCK_FREE (default), SYNCHRONIZED and NONE are explained only in the Javadoc of SynchronizationStrategy.

Bucket API, this page

Bucket4j: clock skew between replicas

Not mentioned for compare-and-swap, lock and select-for-update backends.

The application JVM’s clock is used unless clientClock is set (SYSTEM_MILLISECONDS is the default for these); entry-processor grids use the server’s clock (AbstractProxyManager).

Distributed rate limiting

Bucket4j: Micrometer example (the reference’s listener example)

SimpleBucketListener exposed through Micrometer gauges, written with Guava collection types.

Bucket4j has no built-in Micrometer integration; the example works with Tags.of and no Guava, and per-bucket listeners are the way to get per-plan numbers.

Observability

Bucket4j: the distributed checklist and expiry

The retention item names Ignite and Ignite 3 as the backends without per-entry expiration.

The 8.21.0 builders also refuse an expirationAfterWrite strategy for JCache, Geode and Hazelcast compare-and-swap (isExpireAfterWriteSupported() is false); JDBC computes expires_at but never deletes rows.

Production checklist, Distributed rate limiting

Bucket4j: dependency snippet

A plain dependency with a version, no BOM.

bucket4j_jdk17-bom exists since 8.21.0 and manages all backends except infinispan-15 and the aggregators (bucket4j-bom).

Overview, this page

Bucket4j: JDK 11 artifacts and the compatibility policy

The backward-compatibility policy promises Maven Central artifacts for two LTS JDKs (17 and 11); the compatibility matrix lists bucket4j-core for JDK 11 and bucket4j_jdk8-* for Java 8 (policy, matrix).

bucket4j_jdk11-* stops at 8.15.0 and is not published for 8.16 to 8.21; bucket4j-core ends at 8.10.1; the matrix predates the 8.11.x rename.

Overview, this page

Bucket4j: link "Java compatibility matrix" (quick start)

The reference links the "Java compatibility matrix" for a Java 8 build.

The link leads to the commercial Java 8 page, not to the repository matrix. Open-source Java 8 artifacts end at 8.10.1.

Overview

Bucket4j: DynamoDB

The 8.7.0 release notes list "support for Amazon DynamoDB has been removed"; the reference has no section on it.

No DynamoDB module in the 8.21.0 repository; bucket4j-dynamodb-sdk-v1 and bucket4j_jdk8-dynamodb-sdk-v1 on Maven Central end at 8.6.0 and are not in the BOM.

Backends

Bucket4j: Infinispan 16 in the 8.21.0 notes

"Support for Infinispan 16.x via a dedicated module".

The new dedicated module is infinispan-15 (Infinispan 15.0.4.Final); the plain infinispan module is the one built against 16.2.2, and infinispan-15 is not in the BOM.

Backends, this page

Bucket4j: release notes of 8.5.0, 8.6.0, 8.20.0 and 8.21.0

8.5.0 lists only the builder; 8.6.0 lists only a tryConsumeAsMuchAsPossible fix; 8.20.0 and 8.21.0 list the same items twice over.

The 8.5.0 deprecations and the 8.6.0 removal of MathType.IEEE_754 are visible only in the source; use Maven Central for which module arrived when.

This page

Bucket4j README at the 8.21.0 tag

The dependency snippet and the documentation links show 8.20.0 and bucket4j.github.io.

The documentation lives at bucket4j.com/8.21.0/; this section links only that form.

This page

Starter README: Maven coordinates

XML examples use the artifact ids servlet, webflux, aop and the group com.giffing.bucket4j.spring.boot.starter.cache for the cache modules.

Maven Central has starter-servlet, starter-webflux, starter-aop and the cache modules in the group com.giffing.bucket4j.spring.boot.starter; the other coordinates answer 404.

Starter

Starter README: filter-method and the Gateway filter

filter-method=servlet # [servlet,webflux,gateway], the Spring Cloud Gateway Global Filter listed as supported, and a version note that Gateway support was dropped in 0.14.0.

GATEWAY was removed from FilterMethod in 0.20.0-RC1; the 0.13.0 and 0.14.0 sources still have the enum value and the Gateway filter factory. After 0.20.0-RC1 only SERVLET, WEBFLUX and JMS remain.

Starter, Gateway, this page

Starter README: version list

> 0.20.0 - Bucket4j 8.16.x - Spring Boot 4.0.0.

The 0.20.0-RC1 parent pom has Spring Boot 4.1.1 and Bucket4j 8.17.0. No GA 0.20.0 exists on Maven Central.

Starter

Starter README: cache-to-use options

Lists "jcache, hazelcast, ignite, redis", and the cache table names jcache-ignite for Ignite and jcache for PostgreSQL.

The values are jcache, hazelcast-spring, hazelcast-reactive, ignite, infinispan, jcache-infinispan, redis-jedis, redis-lettuce, redis-redisson and (0.20.0-RC1) postgresql. There is no caffeine value: Caffeine works through jcache.

Starter

Starter README: bucket4j.methods[].rate-limits

The properties example writes bucket4j.methods[0].rate-limits[0]…​.

The property is the singular rate-limit; the README’s YAML example is correct.

Starter

Starter README: refill-speed=intervall

The method example uses intervall.

The enum constant is INTERVAL (GREEDY is the default); intervall is not a valid value.

Starter

Starter README: @RateLimiting(ratePerMethod)

It adds the method name to the key.

Nothing reads the attribute in 0.13.0, 0.14.0 or 0.20.0-RC1; the key always starts with the method name, so overloads share a bucket.

Starter

Starter README: hide-http-response-headers

"Does not affect custom headers" (also the property’s Javadoc).

The servlet and WebFlux filters hide the custom http-response-headers as well.

Starter

Starter README: metrics.enabled

A property of the filter metrics.

It is never read; use metrics.types.

Starter

Starter README: spring-boot-starter-aop

Method dependencies: add spring-boot-starter-aop.

That starter does not exist in Spring Boot 4; AspectJ comes from spring-boot-starter-aspectj.

Starter, Manual integration

Starter README: filter-order

"Set it to a number higher than zero to run after Spring Security."

The default is Ordered.HIGHEST_PRECEDENCE + 10 in all three versions, and any value above -100 puts the filter after Spring Security’s chain; zero is convenient, not a threshold.

Starter

Gateway docs: Caffeine example

new CaffeineProxyManager<>(builder, Duration).

Both CaffeineProxyManager constructors are @Deprecated in 8.21.0; use Bucket4jCaffeine.builderFor(…​).build().asAsync().

Gateway

Gateway docs: Bucket4j version link

The reference links the Bucket4j 8.7.0 documentation.

The documentation of the version you use is at bucket4j.com/8.21.0/; this section cites that.

Gateway

Gateway docs: AsyncProxyMananger

The WebFlux section spells the type with a typo.

The type is io.github.bucket4j.distributed.proxy.AsyncProxyManager; a ProxyManager becomes one with asAsync().

Gateway

Gateway docs: "Spring Cloud Gateway manages Bucket4j 8.15.0"

Read as "the gateway sets the Bucket4j version".

True only of the gateway’s own build: spring-boot-dependencies 4.1.1, spring-cloud-dependencies 2025.1.3 and spring-cloud-gateway-dependencies 5.0.3 do not manage Bucket4j, and both gateway modules declare bucket4j_jdk17-core as <optional>. Import bucket4j_jdk17-bom and declare the jars yourself.

Gateway

Gateway docs: deny-empty-key

spring.cloud.gateway.filter.request-rate-limiter.deny-empty-key (the old prefix).

The code binds spring.cloud.gateway.server.webflux.filter.request-rate-limiter; the old prefix is silently ignored in 5.0.3.

Gateway

Gateway docs: capacity and the Web MVC options

capacity is described as the tokens "in a single second"; configurationBuilder is not documented.

The refill period is whatever refillPeriod or period says; configurationBuilder replaces the default one-greedy-bandwidth configuration.

Gateway

The table has 39 rows, and it gathers what the rest of the Bucket4j part of this section states in prose. When the reference, a README or a documentation page and one of these rows disagree, trust the 8.21.0 source files linked in the row, or the source of the starter and gateway tag named in it, and treat the official page as a good description of the concept rather than of the signature.

Pitfalls

Table 8. Pitfalls when migrating
Pitfall What happens What to do

A dependency bot that follows bucket4j-core

It never proposes anything newer than 8.10.1, because the artifact id changed in 8.11.x

Switch to bucket4j_jdk17-* explicitly and import bucket4j_jdk17-bom

Both bucket4j-core and bucket4j_jdk17-core on the classpath

Duplicate io.github.bucket4j classes, and whichever comes first wins

Exclude the legacy artifact from every transitive dependency (an old starter, an internal library)

Staying on Java 11 and bumping to 8.21.0

The jars need Java 17

Stay on bucket4j_jdk11-* 8.15.0 until the runtime moves, or upgrade the JDK

Bucket4j.builder() after the upgrade

The class is gone since 8.5.0: a compile error

Bucket.builder() and BucketConfiguration.builder()

Converting Bandwidth.simple(100, 1 min) to a refill of 10

simple refills the whole capacity every period; the two are not the same bucket

Use refillGreedy(100, period) for the equivalent of simple, as in the mapping table

Adding id(…​) while converting a live distributed bucket

replaceConfiguration or implicit replacement treats the bandwidth as a different one and loses the inherited tokens

Give both the old and the new bandwidth the same id, or none

Declaring the aggregators bucket4j_jdk17-redis, -hazelcast-all or -infinispan-all

The newest build is 8.14.0, with no 8.21.0

Use the individual modules and the BOM

Starter 0.20.0-RC1 keeps Bucket4j 8.17.0

Fixes and modules of 8.18 to 8.21 are missing

Import bucket4j_jdk17-bom 8.21.0 before the starter

starter-servlet and starter-webflux together

They cannot share a classpath

One feature module per application

filter-method: gateway on 0.20.0-RC1

The value no longer binds and the Gateway filter is gone

Use the gateway’s native Bucket4j limiter

Following a reference example that does not compile

withClientSideConfig, Bandwidth.builder(limit → …​), a TimeMeter without isWallClockBased(), javax.servlet

Use the discrepancy table above

What changed

  • 7.x to 8.0: JDK 11 baseline, groupId com.bucket4j, dedicated bucket4j_jdk8-* artifacts for Java 8.

  • 8.5.0: the Bandwidth.builder() and addLimit(limit → …​) API; Bandwidth.simple, Bandwidth.classic, withInitialTokens, withId and the Refill class are deprecated; the Bucket4j factory class is gone.

  • 8.6.0: MathType.IEEE_754 removed.

  • 8.7.0: Amazon DynamoDB support removed.

  • 8.11.x: bucket4j_jdk17-* artifacts, Redis split into redis-common plus a client, unified builders for every ProxyManager.

  • 8.15.0: the last bucket4j_jdk11-* release.

  • 8.16 to 8.21: GLIDE, Vert.x, Couchbase, Geode, Memcached, Cassandra, Ignite 3, infinispan-15 and coherence-21, the compare-and-swap retry mechanism, Redis Sentinel through Lettuce, and in 8.21.0 the bucket4j_jdk17-bom.

  • Starter: 0.13.0 on the jdk17 artifacts, 0.14.0 on Spring Boot 4, 0.20.0-RC1 on the new module layout without the Gateway filter; no GA 0.20.0 yet.

  • Version 9: not released; the policy removes deprecated API only in a major release, so everything on this page that avoids deprecated API should carry over. The state of the line is on the previous-releases page.