Consider the following repository interface declaration:
interface KotlinUserRepository : Repository<User, String> {
fun findById(username: String): User
fun search(username: String) = findById(username)
}
I'm declaring a default interface method search(…)
that defaults to invoking findById(…)
.
Starting my application fails with:
org.springframework.data.mapping.PropertyReferenceException: No property Search found for type User!
How can I use Kotlin default methods with Spring Data repository interfaces and prevent PropertyReferenceException
?
Default available methods CrudRepository and PagingAndSortingRepository offer default methods such as: findAll, findAllById, findById, deleteAll, deleteById, save, saveAll.
If your code targets Java 8 and you want to generate default methods in interfaces, you can use one of two new modes in Kotlin 1.4: -Xjvm-default=all or -Xjvm-default=all-compatibility .
In Kotlin, the interface works exactly similar to Java 8, which means they can contain method implementation as well as abstract methods declaration.
Spring Boot Gradle plugin automatically uses the Kotlin version declared via the Kotlin Gradle plugin. You can now take a deeper look at the generated application.
Kotlin 1.1/1.2 compiles default methods to abstract interface methods in the first place. It's not possible to use Kotlin's default methods in Spring Data repository interfaces.
Kotlin allows default interface methods with a Java runtime version 1.6. JVM-level default interface methods were introduced with Java 1.8. This causes Kotlin to use a different approach to compile default interface methods than Java does.
The code from KotlinUserRepository
compiles to:
interface KotlinUserRepository extends Repository {
User findById(String username);
User search(String username);
@Metadata(…)
public static final class DefaultImpls {
public static User search(KotlinUserRepository $this, String username) {
Intrinsics.checkParameterIsNotNull(username, "username");
return $this.findById(username);
}
}
}
The method search(…)
compiles to an abstract interface method. The implementation bit compiles to a class DefaultImpls
which reflects the default method signature. A class wanting to implement KotlinUserRepository
is required to implement search(…)
. Using the interface in a pure Kotlin environment will let the Kotlin compiler create the implementation bits.
Spring Data repositories work with proxies underneath. Every method on a repository must be either:
In this case, search(…)
is not implemented by any custom code according to how you'd implement a Java interface. Spring Data attempts to derive a query and considers search(…)
as property of the User
domain class. Lookup fails and throws PropertyReferenceException
.
This is a known limitation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With