Search code examples
javakotlinspring-data

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?


Solution

  • 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