Disclaimer: I am not sure wether this issues belongs to Gradle or Spring Boot.
Two different issues, but in both cases Springs dependency excludes are ignored.
Case 1
Given the following Gradle build script:
plugins {
`java-library`
}
repositories {
jcenter()
maven { url = uri("https://repo.spring.io/libs-milestone") }
}
dependencies {
api(platform("org.springframework.boot:spring-boot-dependencies:2.3.0.RC1"))
api("org.springframework.boot:spring-boot-starter-quartz")
}
With Gradle 5.2.1 you will end with following quartz dependencies on the classpath:
\--- org.springframework.boot:spring-boot-starter-quartz -> 2.3.0.RC1
\--- org.quartz-scheduler:quartz:2.3.2
+--- com.mchange:mchange-commons-java:0.2.15
\--- org.slf4j:slf4j-api:1.7.7 -> 1.7.30
With Gradle 6.4 you will end with following quartz dependencies on the classpath:
\--- org.springframework.boot:spring-boot-starter-quartz -> 2.3.0.RC1
\--- org.quartz-scheduler:quartz -> 2.3.2
+--- com.mchange:c3p0:0.9.5.4
| \--- com.mchange:mchange-commons-java:0.2.15
+--- com.mchange:mchange-commons-java:0.2.15
+--- com.zaxxer:HikariCP-java7:2.4.13
| \--- org.slf4j:slf4j-api:1.7.21 -> 1.7.30
\--- org.slf4j:slf4j-api:1.7.7 -> 1.7.30
I assume that this change of behavior occurs because Gradle 6.4 uses Gradle Module Metadata and Gradle 5 uses Maven BOMs.
Case 2
Given the following dependency declaration:
dependencies {
api(platform("org.springframework.boot:spring-boot-dependencies:2.3.0.RC1"))
api("org.springframework.boot:spring-boot-starter-data-jpa")
}
The following transitive hibernate-core dependencies will be added to the classpath. Interestingly, exclusions work for spring-boot-starter-data-jpa but not for spring-boot-starter-quartz.
\--- org.springframework.boot:spring-boot-starter-data-jpa -> 2.3.0.RC1
+--- org.hibernate:hibernate-core -> 5.4.14.Final
| +--- org.jboss.logging:jboss-logging:3.3.2.Final -> 3.4.1.Final
| +--- org.javassist:javassist:3.24.0-GA
| +--- net.bytebuddy:byte-buddy:1.10.7 -> 1.10.10
| +--- antlr:antlr:2.7.7
| +--- org.jboss:jandex:2.1.1.Final
| +--- com.fasterxml:classmate:1.5.1
| +--- org.dom4j:dom4j:2.1.1
| +--- org.hibernate.common:hibernate-commons-annotations:5.1.0.Final
| | \--- org.jboss.logging:jboss-logging:3.3.2.Final -> 3.4.1.Final
| \--- org.glassfish.jaxb:jaxb-runtime:2.3.1 -> 2.3.3
| +--- jakarta.xml.bind:jakarta.xml.bind-api:2.3.3
| +--- org.glassfish.jaxb:txw2:2.3.3
| +--- com.sun.istack:istack-commons-runtime:3.0.11
| \--- com.sun.activation:jakarta.activation:1.2.2
If I add hibernate-jcache, which also depends on hibernate-core, the result is that the dependency excludes defined via Spring Boot are "ignored".
dependencies {
api(platform("org.springframework.boot:spring-boot-dependencies:2.3.0.RC1"))
api("org.springframework.boot:spring-boot-starter-data-jpa")
api("org.hibernate:hibernate-jcache")
}
The following transitive hibernate libraries will also be added tom the classpath, as long as I don't exclude them manually.
+--- org.springframework.boot:spring-boot-starter-data-jpa -> 2.3.0.RC1
| +--- org.hibernate:hibernate-core -> 5.4.14.Final
| | +--- javax.persistence:javax.persistence-api:2.2
| | +--- org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec:1.1.1.Final
| | +--- javax.activation:javax.activation-api:1.2.0
| | +--- javax.xml.bind:jaxb-api:2.3.1
| | | \--- javax.activation:javax.activation-api:1.2.0
Comment From: wilkinsona
Thanks for the report.
In the Quartz case, the excludes are defined in spring-boot-dependencies
where they're then published in the pom.xml. They don't, however, appear in the module.json file. We currently apply them manually to the XML that Gradle generates from the dependency constraints. I don't know if it's possible for use to add exclusions to the constraints themselves so that they'd appear in the module.json as well (and perhaps make it into the pom.xml
automatically as well).
@melix is it possible to specify exclusions in the constraints described by Gradle's module metadata? I looked at the documentation and didn't spot any information about exclusions. Specifically, we'd like to include exclusions in the module metadata for our spring-boot-dependencies
Java platform. We add the constraints like this at the moment:
this.dependencyHandler.getConstraints().add(JavaPlatformPlugin.API_CONFIGURATION_NAME,
createDependencyNotation(group.getId(), module.getName(), library.getVersion()));
this.dependencyHandler
is a org.gradle.api.artifacts.dsl.DependencyHandler
. Is it possible to declare exclusions at the same time?
We'd like to publish module metadata for our platform that results in the same behaviour as you'd get from a Maven bom that contains something like the following:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>${quartz.version}</version>
<exclusions>
<exclusion>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
</exclusion>
<exclusion>
<groupId>com.zaxxer</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependencies>
</dependencyManagement>
Comment From: dawi
I'm afraid that it's not possible: https://github.com/gradle/gradle/issues/12214.
I asked this question, because I had the same issue with our custom platform module. This platform is based on the spring boot platform and I wanted to add additional libraries with exclusions, so that subprojects (and we have quite a few of them) don't have to exclude everything again.
Since it was not possible to define the exclusions, I added constraints to out platform module, so that subprojects at least recognize that they may have to exclude some libraries. This constraints also help if you add or update dependencies. Because the build will fail if a excluded dependency is accidentally leaks into the classpath.
But I am not happy with this solution, because it feels like it should be able to define this via Gradle platform.
My biggest concern currently is, that this Gradle dependency management is getting to complex for "normal" users (like me). I really spent weeks just to get the dependency management right and I still have issues and am not sure if I am doing it right.
Just to get an idea, this is a short extract from our constraints, which we mainly maintain, because we cannot rely on Gradle/Spring exclusion of transitive dependencies:
constraints {
api("org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec") {
version {
rejectAll()
because("We should use jakarta.transaction-api instead.")
}
}
api("org.apache.geronimo.specs:geronimo-jms_1.1_spec") {
version {
rejectAll()
because("We should use jakarta.jms-api instead.")
}
}
api("javax.servlet:javax.servlet-api") {
version {
rejectAll()
because("We should use jakarta.servlet-api instead.")
}
}
api("com.zaxxer:HikariCP-java7") {
version {
rejectAll()
because("Optional quartz-scheduler dependency. Not needed in our case.")
}
}
api("com.mchange:c3p0") {
version {
rejectAll()
because("Optional quartz-scheduler dependency. Not needed in our case.")
}
}
api("com.mchange:mchange-commons-java") {
version {
rejectAll()
because("Optional quartz-scheduler dependency. Not needed in our case.")
}
}
}
Comment From: wilkinsona
Looking at the second case, this is standard behaviour for Gradle. While we have some exclusions declared on hibernate-core
in spring-boot-starter-data-jpa
, adding a dependency on hibernate-jcache
adds another route to hibernate-core
to the dependency graph. This route does not have the same exclusions so the unwanted dependencies are pulled in. This happens because, unlike Maven, Gradle requires exclusions to be declared on every route to a dependency for those exclusions to be effective.
Comment From: wilkinsona
Thinking about this issue got me wondering if we could provide some dependency substitutions out of the box. I've opened https://github.com/spring-projects/spring-boot/issues/21359 to explore that idea.
Comment From: wilkinsona
We currently have 22 exclusions declared in spring-boot-dependencies
. We could move/copy those to the various starters, but not all managed dependencies have a starter. Liquibase is one example of such a dependency. The alternative is to disable the publication of Gradle's module metadata for spring-boot-dependencies
. In its absence, Gradle will use the pom file where it will find and honour the exclusions. It's a little surprising that Gradle's behaviour is better-suited to our needs when it's using Maven's metadata rather than its own, but it may well be our best option here.
Comment From: dawi
Regarding the second case: I would appreciate if Gradle would offer an additional simpler solution, but yes, currently there is no other option then to add the exclusions to all the starter poms. But it will also have downsides:
- It only works if your project uses the starters. You would still get all transitive dependencies when you add a hibernate-core dependency to your project. Something that would not be the case with Maven BOMs, and this is hard to explain to users.
- From a maintainers perspective: You may have to repeat the same exclusion multiple times over various starters. Again something that would be easy with Maven BOMs.
- Beside the technical issues, almost certainly there will be users that just don't care and they will end up with libraries on their classpath that shouldn't be there.
Since Gradle Module Metadata is still young, and not yet widely used, maybe it would be good to talk to them about this topics?
Last but not least: @wilkinsona Thank you for your answer and your great work. :)
Comment From: ljacomet
Hey there,
Just to confirm that Gradle does not support defining exclusions on dependency constraints.
The support for excludes defined in Maven dependencyManagement
was added when BOM support was added to Gradle, so that producers of Maven BOM would not have to deal with too big differences between Maven and Gradle consumers.
What happens in the Spring Boot build is that the excludes are added on the generated POM file, which indicates this is not a supported thing from the Gradle's perspective and thus indeed is not replicated in the produced Gradle Module Metadata file.
However the discussion on this issue and the suggested workaround with rejectAll()
constraints has led the Gradle team to re-open the conversation internally. Updates will be done on gradle/gradle#12214. Note that this is not a statement that a change will be made.
Comment From: wilkinsona
Thanks very much, @ljacomet. We'll disable publication of Gradle module metadata for spring-boot-dependencies
for now.
Comment From: wilkinsona
A recent change has broken some of the integration tests in start.spring.io. Given the timing of the failures starting to occur, our best guess is that it is this change that has caused them, although we do not really understand why. There is a usage of enforcedPlatform
in spring-boot-parent
which I believe should be platform
along with our internal dependency management plugin so that it isn't enforced for external consumers but is within our build.
Comment From: snicoll
https://github.com/spring-projects/spring-boot/commit/c35ed9100b0fe1b8bd7690e6de801e4ad5f784f3 fixed it.