Spring Boot Resilience4J: Unresolved dependency - spring

I am trying to implement Resilience4j in a sample application and I was following the demo application from the official documentation. But adding the below dependencies gives me a gradle error:
Unresolved dependency: org.springframework.cloud spring-cloud-starter-circuitbreaker-reactor-resilience4j
Here is my build.gradle
plugins {
id 'org.springframework.boot' version '2.7.2'
id 'io.spring.dependency-management' version '1.0.12.RELEASE'
id 'java'
}
group = 'com.thomsoncodes'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
repositories {
mavenCentral()
}
ext {
set('springCloudVersion', "2021.0.3")
}
ext{
resilience4jVersion = '1.7.1'
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.projectreactor:reactor-test'
compile('org.springframework.boot:spring-boot-starter-aop')
compile("io.github.resilience4j:resilience4j-spring-boot2:${resilience4jVersion}")
compile("io.github.resilience4j:resilience4j-all:${resilience4jVersion}") // Optional, only required when you want to use the Decorators class
compile("io.github.resilience4j:resilience4j-reactor:${resilience4jVersion}")
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
tasks.named('test') {
useJUnitPlatform()
}

try to change the version to 1.7.0. I have had the similar error some days ago. For some reason there seems to be a problem with transitive dependencies related to the spring-boot2 artifact. The artifact itself is in the version 1.7.1 but all transitive dependencies are 1.7.0.
After the change all versions were then 1.7.0 and it worked.

The issue is caused by using spring-cloud in your project, too. spring-cloud's bom is adding spring-cloud-circuitbreaker-dependencies-x.y.z.pom into your classpath defining its own dependency for resilience4j. At the time of my investigation spring-cloud version 2021.0.5 defines still resilience4j.version 1.7.0.
The issue was already reported in an older post here: https://github.com/resilience4j/resilience4j/issues/848
If you want to use a more recent version of resilience4j you can overwrite the dependency imports in build.gradle on your own or exclude the imports originating from spring-cloud.
For me utilizing Maven instead of Gradle adding another dependency for resilience4j-bom solved the issue.
<properties>
<resilience4j.version>2.0.2</resilience4j.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-bom</artifactId>
<version>${resilience4j.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

Related

Gradle maven-publish dependency scope

I have a pretty simple Gradle Kotlin project.
plugins {
id 'application'
id 'maven-publish'
}
repositories { mavenCentral() }
dependencies {
compile 'com.google.guava:guava:31.1-jre' // 'compile' is deprecated
}
publishing {
publications {
maven(MavenPublication) {
groupId = 'de.mabe'; artifactId = 'project1'; version = '1.0'
from components.java
}
}
}
When I start gradle publishToMavenLocal it generates a correct pom file with a correct dependency:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
<scope>compile</scope> <!-- this scope is important -->
</dependency>
Now I replaced the compile with implementation in the gradle script.
implementation 'com.google.guava:guava:31.1-jre'
Unexpectedly this changes the scope in the pom file from compile to runtime.
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
<scope>runtime</scope> <!-- compile EXPECTED ?!?! -->
</dependency>
What do I have to do to get the previous pom back?
That is by design. The semantics of the implementation configuration is to declare dependencies that are internal to a module. By mapping it to the runtime scope of a Maven pom, it ensures that it doesn't leak onto the compilation classpath of consumers. This has a few advantages like being more free to swap out transitive depencies with less risk of affecting consuming modules, to make compilation faster and more.
If you need to make a transitive dependency part of the compile scope, i.e. expose it on the compilation classpath of consuming projects, you need to use the api configuration instead. This is available by applying the java-library plugin.
For example (Groovy DSL):
plugins {
id 'java-library'
id 'maven-publish'
}
dependencies {
implementation 'org.apache.commons:commons-math3:3.6.1' // <- Maps to the Maven runtime scope
api 'com.google.guava:guava:30.1.1-jre' // <- Maps to the Maven compile scope
}
publishing {
publications {
maven(MavenPublication) {
groupId = 'de.mabe'; artifactId = 'project1'; version = '1.0'
from components.java
}
}
}
You can read more about the separation between API and implementation in the Gradle user guide here.

Can I pull the version for the Gradle org.springframework.boot plugin from my Gradle platform definition

I really like the Gradle java-platform feature. I've created my own platform that bundles spring-boot-dependencies along with other things. Now I have (shortened for clarity):
plugins {
id 'org.springframework.boot' version '2.4.1'
}
dependencies {
implementation platform("my-group:my-base-bom:1.0.0")
}
And I'd like the spring boot plugin version to automatically adjust to match the version of spring-boot-dependencies that is bundled in my platform (so if the platform went to SB 2.5.0 then plugin would do the same without my needing to change the build.gradle.
I can't figure out how to do it though without resorting to external variables. Is it possible?
Not possible. Currently, there are (3) ways to define versions for plugins:
In the Gradle file directly:
// build.gradle.kts
plugins {
id("org.springframework.boot") version "2.4.1"
}
In the plugins dependencies spec:
// settings.gradle.kts
pluginManagement {
plugins {
id("org.springframework.boot") version "2.4.1"
}
}
or with a resolution rule:
// settings.gradle.kts
pluginManagement {
resolutionStrategy {
eachPlugin {
if (requested.id.id == "org.springframework.boot") {
useVersion("2.4.1")
}
}
}
}
All of which do not accept a platform, only a single version variable.
Another way I tested, but ultimately did not work was utilizing the buildscript:
// build.gradle.kts
buildscript {
dependencies {
classpath(platform("io.mateo.sample:platform-bom:1.0.0-SNAPSHOT"))
classpath("org.springframework.boot:spring-boot-gradle-plugin")
}
}
As mentioned at the start, it's not possible.
Your custom platform can provide an opinion about which version of the Spring Boot Gradle plugin you want clients to use (especially since it's not included in the spring-boot-dependencies BOM).
Here are the relevant parts of an example platform's build.gradle.kts file, for example:
plugins {
`java-platform`
}
javaPlatform {
allowDependencies()
}
dependencies {
// This platform extends the Spring Boot platform.
api(platform("org.springframework.boot:spring-boot-dependencies:2.7.6"))
constraints {
// Provide an opinion about which version of the Spring Boot Gradle plugin clients
// should use since it's not included in the standard spring-boot-dependencies BOM.
api("org.springframework.boot:spring-boot-gradle-plugin:2.7.6")
}
}
That will generate a BOM that aligns both spring-boot-gradle-plugin and spring-boot-dependencies:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-gradle-plugin</artifactId>
<version>2.7.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.7.6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Your client projects can then just depend upon your platform and inherit its opinion about the Spring Boot version using something like this:
buildSrc/build.gradle.kts:
// Pull in the version of the Spring Boot Gradle plugin specified by your
// platform, making it available to your regular build script.
dependencies {
implementation(enforcedPlatform("my-group:my-base-bom:1.0.0"))
implementation("org.springframework.boot:spring-boot-gradle-plugin")
}
build.gradle.kts:
plugins {
id("org.springframework.boot") // version inherited from your platform
}
dependencies {
// It's necessary to specify it for each configuration.
implementation(enforcedPlatform("my-group:my-base-bom:1.0.0"))
// Pull in any normal Spring Boot-managed dependencies you need (versions come from platform).
implementation("org.springframework.boot:spring-boot-starter-web")
}
Of course, you could also use Gradle version catalogs to centralize versions, which are inlined for clarity in the examples.

How to use gradle feature variant dependecies in tests?

I am migrating a Maven library project to Gradle. The original project also has optional dependencies. I use the java-library plugin but moving the formerly optional dependencies to implementation results in runtime dependencies instead of compile. So I tried the gradle feature variants which results in the right dependencies in the pom.xml. But doing so results is failing test compile as the dependencies of the feature variant are missing on the test compile classpath!
Here is my current setup in build.gradle:
apply plugin: 'java'
apply plugin: 'java-library'
apply plugin: 'maven-publish'
sourceCompatibility = 1.8
java {
registerFeature('oSupport') {
usingSourceSet(sourceSets.main)
}
}
dependencies {
api 'my.compile:dep-a:1.0.0'
implementation 'my.runtime:dep-i:1.0.0'
oSupportApi 'my.optional:dep-o:1.0.0'
}
Let's assume there is a class O available from my.optional:dep-o. If I import O in any class in src/main/java it works perfectly. Also the dependencies are exported right to Maven (using gradle generatePomFileForMavenJavaPublication, see the dependencies from the generated pom.xml below). But any test in src/test/java using class O will not compile (import my.optional.O; creates error: package my.optional does not exist)
<dependencies>
<dependency>
<groupId>my.compile</groupId>
<artifactId>dep-a</artifactId>
<version>1.0.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>my.rintime</groupId>
<artifactId>dep-r</artifactId>
<version>1.0.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>my.optional</groupId>
<artifactId>dep-0</artifactId>
<version>1.0.0</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
</dependencies>
How to solve this? I know I could have used the nebula.optional-base plugin instead of the buildin Gradle feature variant but I would prefer the new gradle builtin support for optional dependencies instead.
PS: I use Java 8 and Gradle 5.6.2
This looks like a bug when the feature source set uses the main source set. Can you report on https://github.com/gradle/gradle/issues?
In the meantime, this should fix it:
configurations {
testCompileClasspath.extendsFrom(oSupportApi)
testRuntimeClasspath.extendsFrom(oSupportApi)
testRuntimeClasspath.extendsFrom(oSupportImplementation)
}
Really weird, I agree with #melix this seems to be a Gradle bug.
The following will fix it but should not be needed, imho:
dependencies {
api 'my.compile:dep-a:1.0.0'
implementation 'my.runtime:dep-i:1.0.0'
oSupportApi 'my.optional:dep-o:1.0.0'
testImplementation(project(":${project.name}")) {
capabilities {
requireCapability("${project.group}:${project.name}-o-support")
}
}
}
For this simplified setup with only one feature dependency could be replaced by testImplementation 'my.optional:dep-o:1.0.0' but for a general larger dependency list this approch avoids repetition of the dependencies as the extendsFrom solution of #melix.

How to use a maven BOM for Spring in Gradle?

I am converting POM to Gradle and one of the things I am stuck at is having dependency management in Gradle like the following that I have in POM:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Edgware.SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Is there a way to have Edgware.SR4 in Gradle as well?
I checked https://docs.gradle.org/4.6/release-notes.html#bom-import but that doesn't really tell me a way on how to utilize Edgware.SR4 BOM.
UPDATE
I finally have my build.gradle as follows that seems to work:
plugins{
id 'org.springframework.boot' version '1.5.8.RELEASE'
}
apply plugin: 'io.spring.dependency-management'
dependencyManagement {
imports {
mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Edgware.SR4'
}
}
This seems to be working fine but wondering if there is any flaw in this approach. Documentation available at https://docs.spring.io/spring-boot/docs/current/gradle-plugin/reference/html/ suggests to use apply false to begin with in
id 'org.springframework.boot' version '1.5.8.RELEASE'
I didn't do that and it worked fine. Wondering why it was suggested like that.
Assuming that you are using Spring Boot and, therefore, already have the Dependency Management Plugin applied, you can import Spring Cloud's bom by adding the following to your build.gradle file:
dependencyManagement {
imports {
mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Edgware.SR4'
}
}
As of today, the latest versions of gradle have a built-in solution.
dependencies {
implementation(platform("org.springframework.cloud:spring-cloud-dependencies:Edgware.SR4"))
}

Jersey + Gradle with maven dependencies not working

Hi I am following the jersey sun documentation. I have deployed before this simple pom.xml before
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-server</artifactId>
<version>1.14</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-grizzly2</artifactId>
<version>1.14</version>
</dependency>
and Add the repository
<url>https://maven.java.net/content/repositories/snapshots/</url>
Nevertheless when I try to do this with gradle, it does not seem to be working, is not downloading the rest of dependencies that requires and aparently I have to explicitly put javax.ws.rs:jsr311-api:1.1.1 and even jersey-core. This is my build.gradle.
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'jetty'
sourceCompatibility = 1.6
repositories {
mavenCentral()
maven {
url 'https://maven.java.net/content/repositories/snapshots/'
}
}
List compileLibraries =['com.sun.jersey:jersey-server:1.14',
'com.sun.jersey:jersey-grizzly2:1.14',
'com.sun.jersey:jersey-core:1.14',
'javax.ws.rs:jsr311-api:1.1.1']
dependencies {
compile (compileLibraries )
testCompile group: 'junit', name: 'junit', version: '4.+'
}
httpPort = 8888
stopPort = 9451
stopKey = 'foo'
Is this proper gradle behaviour? How can I do as same as with maven?
Edit
Just for the sake of this and if somebody is interesting in seeing a gradle build file that work with gradle you can go to
https://github.com/necronet/XTradeJerseyimpl/
Thanks!!
Seems that the dependency from jersey-server to jersey-core isn't being properly interpreted by Gradle. Looking at the pom shows that the jersey-core dependency is in a profile, which is likely why it's not being picked up. And jersey-core has the dependency on jsr311. Sounds like Gradle should probably account for the profile marked 'activeByDefault' in cases like these.
That said, you've already hit upon the solution, which is to specify the two jars directly - and it's still less lines to configure than the maven xml :)
Also, it looks like all the jars you need can be found in mavenCentral, so the snapshot repository isn't contributing anything.
This doesn't solve the need to explicitly mention those two extra jars, but I hope this explains why, and you might want to raise an issue on Gradle Jira if you think this should be addressed.

Resources