I have a Gradle project with the following configuration:
plugins {
id 'org.springframework.boot' version '3.5.0'
id 'io.spring.dependency-management' version '1.1.7'
id 'java'
}
ext {
set('springCloudVersion', "2025.0.0")
}
implementation 'com.bucket4j:bucket4j_jdk17-core:8.15.0'
implementation 'com.bucket4j:bucket4j_jdk17-redis-common:8.15.0'
implementation 'com.bucket4j:bucket4j_jdk17-lettuce:8.15.0'
implementation 'org.springframework.cloud:spring-cloud-kubernetes-client-discovery'
During startup I get this error:
java.lang.NoSuchMethodError: 'void io.github.bucket4j.distributed.proxy.ClientSideConfig.<init>(io.github.bucket4j.distributed.versioning.Version, java.util.Optional, io.github.bucket4j.distributed.proxy.ExecutionStrategy, java.util.Optional, java.util.Optional, io.github.bucket4j.BucketListener, io.github.bucket4j.distributed.proxy.RecoveryStrategy)'
at io.github.bucket4j.distributed.proxy.AbstractProxyManagerBuilder.getClientSideConfig(AbstractProxyManagerBuilder.java:236) ~[bucket4j_jdk17-core-8.15.0.jar!/:na]
at io.github.bucket4j.redis.lettuce.cas.LettuceBasedProxyManager.<init>(LettuceBasedProxyManager.java:161) ~[bucket4j_jdk17-lettuce-8.15.0.jar!/:8.15.0]
at io.github.bucket4j.redis.lettuce.Bucket4jLettuce$LettuceBasedProxyManagerBuilder.build(Bucket4jLettuce.java:152) ~[bucket4j_jdk17-lettuce-8.15.0.jar!/:8.15.0]
The called method's class, io.github.bucket4j.distributed.proxy.ClientSideConfig, is available from the following locations:
jar:nested:/gateway.jar/!BOOT-INF/lib/bucket4j-core-7.6.0.jar!/io/github/bucket4j/distributed/proxy/ClientSideConfig.class
jar:nested:/gateway.jar/!BOOT-INF/lib/bucket4j_jdk17-core-8.15.0.jar!/io/github/bucket4j/distributed/proxy/ClientSideConfig.class
After dependency lookup, I see that there are 2 Bucket4j versions in my project:
What are the options to resolve this issue?
As you've already pointed out, there is a dependency collision for bucket4j-core. By running the command gradle dependencies, the console lists bucket4j_jdk17-core:8.15.0 as the dependency explicitly imported, while bucket4j-core:7.6.0 as a transitive dependency coming from client-java-extended:19.0.2 > spring-cloud-kubernetes-client-autoconfig:3.3.0 > spring-cloud-kubernetes-client-discovery:3.3.0. Here is also an abridged snippet of the command's output:
+--- com.bucket4j:bucket4j_jdk17-lettuce:8.15.0
| +--- com.bucket4j:bucket4j_jdk17-core:8.15.0
| \--- com.bucket4j:bucket4j_jdk17-redis-common:8.15.0
| \--- com.bucket4j:bucket4j_jdk17-core:8.15.0
+--- org.springframework.cloud:spring-cloud-dependencies:2025.0.0
| ...
\--- org.springframework.cloud:spring-cloud-kubernetes-client-discovery -> 3.3.0
+--- org.springframework.cloud:spring-cloud-kubernetes-client-autoconfig:3.3.0
| | ...
| +--- io.kubernetes:client-java-extended:19.0.2
| | ...
| | +--- com.github.vladimir-bukhtoyarov:bucket4j-core:7.6.0
These two dependencies are the ones that actually clash. Your code is compiled against the ClientSideConfig constructor from version 8.15.0, but at runtime the class is loaded from version 7.6.0. Since the older version does not provide a constructor with that signature, a NoSuchMethodError is thrown.
In your scenario, even though you could technically exclude or override transitive dependencies, it could potentially break the dependencies that are based on them. So, to fix your issue, the only two options I see here are
Downgrading bucket4j_jdk17-core, bucket4j_jdk17-redis-common, and bucket4j_jdk17-lettuce to 7.6.0, if you don't strictly need 8.15.0, so that all dependencies are harmonized.
Use package relocation (or shading) on the dependencies explicitly imported to avoid JAR hell (classpath conflicts).
Package relocation works by relocating the classes brought by your dependencies to a different custom classpath. Keep in mind that this solution works only if the relocated dependency or its transitive dependencies don't heavily rely on direct class reference (reflection, string-based referencing, etc.). However, in this scenario, relocation is generally safe since Bucket4j does not rely on reflection or ServiceLoader mechanisms, but you should thoroughly test all functionality after shading.
Also, as a side note, we can see from the dependency tree that bucket4j_jdk17-lettuce already imports bucket4j_jdk17-core and bucket4j_jdk17-redis-common, so there is no need to re-import them explicitly. This simplifies the build.gradle and the configurations for the shadow plugin.
A snippet of your build.gradle, based on what you shared, would look like this:
plugins {
id 'org.springframework.boot' version '3.5.0'
id 'io.spring.dependency-management' version '1.1.7'
id 'java'
// Add ShadowJar plugin
id 'com.github.johnrengelman.shadow' version '8.1.1'
}
repositories {
mavenCentral()
}
group = 'com.yourcompany'
version = '1.2.3'
ext {
set('springCloudVersion', "2025.0.0")
}
dependencies {
// remove core and redis-commons and leave only lettuce
implementation 'com.bucket4j:bucket4j_jdk17-lettuce:8.15.0'
implementation platform("org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}")
implementation 'org.springframework.cloud:spring-cloud-kubernetes-client-discovery'
}
shadowJar {
archiveClassifier.set("")
// relocate bucket4j-core, bucket4j-redis-commons, and bucket4j-lettuce
// since their classes are respectively under
// - 'io.github.bucket4j'
// - 'io.github.bucket4j.redis'
// - 'io.github.bucket4j.redis.lettuce'
// So, this single pattern relocates all three of them
relocate ("io.github.bucket4j", "com.yourcompany.shadow.bucket4j")
}