-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Description
Summary
After upgrading from Spring Data JPA 4.0.0-M4 to 4.0.0-M5 (Spring Boot 4.0.0-M1 → 4.0.0-M2), custom JPA repository implementations fail to compile due to a type constraint mismatch between the QueryByExampleExecutor.findBy() interface method and the SimpleJpaRepository implementation.
Environment
Spring Boot: 4.0.0-M2 (upgraded from 4.0.0-M1)
Spring Data JPA: 4.0.0-M5 (upgraded from 4.0.0-M4)
Kotlin: 2.2.0
JVM Target: 24
Build Tool: Gradle 9.0.0
OS: macOS Sonoma 14.5
IDE: IntelliJ IDEA Ultimate
Problem Description
The QueryByExampleExecutor.findBy()
method signature was updated to support nullable return types:
New Interface Signature (4.0.0-M5):
<S extends T, R extends @Nullable Object> R findBy(Example<S> example, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction);
Previous Interface Signature (4.0.0-M4):
<S extends T, R> R findBy(Example<S> example, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction);
However, SimpleJpaRepository
was not updated to match this new signature, creating a type constraint conflict:
Interface allows: R extends @Nullable Object
(nullable types)
SimpleJpaRepository requires: R extends Object
(non-nullable types only)
Compilation Error

Impact
This affects any custom repository implementation that extends SimpleJpaRepository
and implements JpaRepository
(which inherits from QueryByExampleExecutor
). In our case, this is our base repository class used throughout the project.
Minimal Reproduction Example
package com.respiroc.user.application.payload
import org.springframework.data.domain.Example
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.support.JpaEntityInformation
import org.springframework.data.jpa.repository.support.SimpleJpaRepository
import org.springframework.data.repository.NoRepositoryBean
import org.springframework.data.repository.query.FluentQuery
import jakarta.persistence.EntityManager
import jakarta.persistence.*
import java.util.function.Function
// Simple entity for demonstration
@Entity
data class TestEntity(
@Id @GeneratedValue
val id: Long = 0,
val name: String = ""
)
// Custom repository interface extending JpaRepository
@NoRepositoryBean
interface CustomRepository<T: Any, ID: Any> : JpaRepository<T, ID> {
// Any custom method
fun customMethod(): String
}
// Custom repository implementation - THIS FAILS TO COMPILE
class CustomRepositoryImpl<T : Any, ID: Any>(
entityInformation: JpaEntityInformation<T, ID>,
entityManager: EntityManager
) : SimpleJpaRepository<T, ID>(entityInformation, entityManager), CustomRepository<T, ID> {
override fun customMethod(): String = "custom implementation"
// COMPILATION ERROR HERE:
// Class 'CustomRepositoryImpl' is not abstract and does not implement abstract member:
// fun <S : T, R> findBy(example: Example<S>, queryFunction: Function<FluentQuery.FetchableFluentQuery<S>, R>): R
}
// Usage example
interface TestEntityRepository : CustomRepository<TestEntity, Long>
Error occurs when:
- Creating a custom repository implementation that extends
SimpleJpaRepository
- The custom repository interface extends
JpaRepository
(which inheritsQueryByExampleExecutor
) - Compiling with Spring Data JPA 4.0.0-M5
Current Workaround
We've implemented the following workaround to resolve the compilation issue:
@Suppress("ACCIDENTAL_OVERRIDE", "UNCHECKED_CAST")
override fun <S : T, R: Any?> findBy(
example: Example<S>,
queryFunction: Function<FluentQuery.FetchableFluentQuery<S>, R>
): R {
return super.findBy(example, queryFunction as Function<FluentQuery.FetchableFluentQuery<S>, Any>) as R
}
Workaround Explanation:
- Type Casting: Cast queryFunction to match SimpleJpaRepository's non-nullable requirement - The queryFunction passed to this override method expects R: Any? but super.findBy() requires R: Any. At runtime they are the same, but we need to cast to pass the compile phase and bridge the nullable/non-nullable type gap.
- @Suppress("UNCHECKED_CAST"): After casting to Any, we cast the result back to R. The compiler cannot verify this type safety, so we suppress the warning since we know the cast is safe.
- @Suppress("ACCIDENTAL_OVERRIDE"): Both override signatures fun <S : T, R: Any?> and fun <S : T, R: Any> have identical JVM signatures due to type erasure. This creates a conflict, so we suppress the accidental override warning to resolve the compilation error.
Testing Results: This workaround has been tested extensively in our application and works correctly with all findBy operations (.all(), .count(), .exists(), .first(), .one()).
Proposed Solution
Update SimpleJpaRepository.findBy()
method signature to match the interface:
// Current SimpleJpaRepository signature
public <S extends T, R extends Object> R findBy(Example<S> example, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction)
// Should be updated to
public <S extends T, R extends @Nullable Object> R findBy(Example<S> example, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction)
Questions
- Is this a bug or intentional breaking change?
- Will SimpleJpaRepository be updated to align with the interface signature?
- Is our workaround approach safe for production use?
- Timeline: When can we expect a proper fix?
Additional Notes
- This issue appears to be Kotlin-specific due to stricter type checking
- The workaround maintains full functionality while bridging the type constraint gap
- No runtime issues have been observed with the workaround solution