After upgrading from Spring Boot 2.2.5 to 2.3.0.M3 I found javax.persistence-api on the classpath, together with jakarta.persistence-api although only jakarta.persistence-api should be on the classpath.

It is possible that this behavior is a result of https://github.com/spring-projects/spring-boot/issues/19609.

This can be verified with the following minimal example.

Given the following build.gradle.kts:

plugins {
    `java-library`
}

repositories {
    mavenCentral()
    maven("https://repo.spring.io/milestone")
}

val springBootVersion = "2.3.0.M3"

dependencies {
    api(platform("org.springframework.boot:spring-boot-dependencies:${springBootVersion}"))
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
}

Running: ./gradlew dependencies --configuration runtimeClasspath shows that the classpath contains both javax.persistence-api:2.2 and jakarta.persistence-api:2.2.3.

runtimeClasspath - Runtime classpath of source set 'main'.
+--- org.springframework.boot:spring-boot-dependencies:2.3.0.M3
|    +--- jakarta.persistence:jakarta.persistence-api:2.2.3 (c)
|    +--- javax.persistence:javax.persistence-api:2.2 (c)
\--- org.springframework.boot:spring-boot-starter-data-jpa -> 2.3.0.M3
     +--- jakarta.persistence:jakarta.persistence-api -> 2.2.3
     +--- org.hibernate:hibernate-core -> 5.4.12.Final
     |    +--- javax.persistence:javax.persistence-api:2.2

Whereas with Spring Boot 2.2.5.RELEASE the classpath only contains jakarta.persistence-api:2.2.3.

runtimeClasspath - Runtime classpath of source set 'main'.
+--- org.springframework.boot:spring-boot-dependencies:2.2.5.RELEASE
|    +--- jakarta.persistence:jakarta.persistence-api:2.2.3 (c)
\--- org.springframework.boot:spring-boot-starter-data-jpa -> 2.2.5.RELEASE
     +--- jakarta.persistence:jakarta.persistence-api:2.2.3

Bonus:

It is not possible to exclude javax.persistence-api manually, the result is still the same as without exclude.

dependencies {
    api(platform("org.springframework.boot:spring-boot-dependencies:${springBootVersion}"))
    implementation("org.springframework.boot:spring-boot-starter-data-jpa") {
        exclude(group = "javax.persistence", module = "javax.persistence-api")
    }
}

Comment From: wilkinsona

Thanks for the report.

I've confirmed the unwanted presence of javax.persistence-api with 2.3.0.M3. It isn't present if you use 2.3.0.M2 (where we didn't publish the module metadata) so that's a pointer towards the metadata being at least part of the cause.

The problem's particularly nasty as our build polices the presence of duplicate classes and resources in all of the starters. No duplicates appear when we're building spring-boot-starter-data-jpa as it appears that in that context its exclusions are effective.

I've also reproduced the problem where it is impossible to exclude javax.persistence-api. Interestingly, this only happens when using Gradle's platform to apply the spring-boot-dependencies bom. If you use the dependency management plugin, this part of the problem does not occur.

It looks like we're hitting a bug in Gradle so I've reached out to them for some help. We'll keep this issue open as we may need to stop publishing the metadata and we can use it to make that change if necessary.

Comment From: ljacomet

Hello,

There is an underlying bug in Gradle indeed, which will be fixed for Gradle 6.3, see gradle/gradle#12536. A workaround for the issue is to use a configuration level exclude:

configurations.runtimeClasspath {
    exclude(module = "javax.persistence-api")
}

Comment From: wilkinsona

For M4, we're going to try bumping the supported version of Gradle 6.x to 6.3. We'll continue to support Gradle 5.6 which isn't affected by this problem. We may adjust course a little depending on the feedback we get on cutting off support for 6.0, 6.1, and 6.2.

Comment From: jjohannes

We just released Gradle 6.3 RC2 that fixes the underlying issue.

Comment From: dawi

I can confirm that with Gradle 6.3 RC2 it works as expected (as with Spring Boot 2.2.5).


@wilkinsona @ljacomet I have one related question regarding transitive dependency exclusions and I am not sure if I should file another issue, if it is expected behavior or if it is being addressed already. Maybe it is related to https://github.com/spring-projects/spring-boot/issues/13957 (which is still open) and https://github.com/gradle/gradle/issues/1473 (which is closed).

With the following dependency declaration:

dependencies {
    api("org.springframework.boot:spring-boot-starter-data-jpa")
}

I get a hibernate-core dependency where all exclusions defined by spring are effective.

If I now add hibernate-jcache as dependency, the exclusions defined by spring are not effective anymore. I have to exclude hibernate-core manually to not get unwanted transitive dependencies like this:

dependencies {
    api("org.springframework.boot:spring-boot-starter-data-jpa")
    implementation("org.hibernate:hibernate-jcache") {
        exclude(module = "hibernate-core")
    }
}

To be sure not to accidentally have unwanted dependencies, I added several constraints like this:

api("javax.persistence:javax.persistence-api") {
    version {
        rejectAll()
        because("We should use jakarta.persistence-api instead.")
    }
}

If I hadn't had this constraints, I might not have noticed this issue in the first place.

Comment From: snicoll

FTR this is no longer blocked as 6.3 is out.