Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use Kotlin default methods with Spring Data repository interfaces?

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?

like image 428
mp911de Avatar asked Mar 09 '18 09:03

mp911de


People also ask

What is the default method of a repository in spring data?

Default available methods CrudRepository and PagingAndSortingRepository offer default methods such as: findAll, findAllById, findById, deleteAll, deleteById, save, saveAll.

How do I set default method in interface in Kotlin?

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 .

CAN interface have method implementation Kotlin?

In Kotlin, the interface works exactly similar to Java 8, which means they can contain method implementation as well as abstract methods declaration.

Can I use Kotlin with spring boot?

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.


1 Answers

TL;DR

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.

Explanation

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:

  1. Implemented by the store-specific repository.
  2. Implemented by a custom implementation.
  3. A Java 8 default method.
  4. Be annotated with a query annotation.
  5. Fit the method naming scheme to allow query derivation.

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.

References

  • DATACMNS-1223 - Kotlin interface default methods are considered query methods.
  • KT-4779 - Generate default methods for implementations in interfaces.
like image 168
mp911de Avatar answered Oct 15 '22 03:10

mp911de