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

As Ben pointed out, you can now (Kotlin 1.2.40+) use @JvmDefault.

interface BatchRepository : PagingAndSortingRepository<Batch, Long> {
    fun getAllByOrderByPriorityAscNameAsc(): List<Batch>

    @JvmDefault
    fun getForAdmin() = getAllByOrderByPriorityAscNameAsc()
}

You will need to enable the option in you build.gradle using something like this:

tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
    kotlinOptions {
        freeCompilerArgs = ['-Xenable-jvm-default']
    }
}

I just tested it on Kotlin 1.2.41 and it works.


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.