I’m running into an issue with Spring data repositories where Spring isn’t finding my methods because the generated Java classes use Integer
in some places and int
in others.
See the edits on java - 405 Method not allowed despite exposing all methods when using RepositoryRestConfiguration.disableDefaultExposure() - Stack Overflow for more detail of the problem I’m having.
My assumption was that specifying a type parameter of Int?
would mean that java.lang.integer
would be used everywhere that type parameter is used however this doesn’t seem to be the case; methods that use the type parameter are being generated as primitive int
arguments.
Java:
interface MyParentInterface<T1, T2> {
T1 get(T2 arg);
}
Kotlin:
interface MyChildInterface : MyParentInterface<Entity, Int?> {
override fun get(arg : Int?) : Entity // Error: get overrides nothing
}
Am I missing something?
well this is a normal kotlin behaviour:
when passing a Int to a function, there is no reason to use Integer, so kotlin compile to int.
but java does not support primitive in generics (until valhalla has destroy everything )… So kotlin has no choice : <Int>
=> <Integer>
when you write Int?
: int
is not nullable (no pointer is used for primitives) so kotlin compiles to Integer
.
anyway this code works:
class Entity {
override fun toString() = "datString"
}
interface MyParentInterface<T1, T2> {
fun get(arg: T2): T1
}
interface MyChildInterface : MyParentInterface<Entity, Int> {
}
fun main(args: Array<String>) {
val x = object : MyChildInterface {
override fun get(arg: Int) = Entity()
}
println(x.get(0))
println(MyChildInterface::class.java.methods.toList())
println(MyChildInterface::class.java.methods[0].invoke(x, 0))
println(MyParentInterface::class.java.methods.toList())
println(MyParentInterface::class.java.methods[0].invoke(x, 0))
}
I’ve put together a simplified example of what I’m trying to do an it works exactly how I’d expect it to
Java:
public interface Repository<T, ID> {
}
public interface CrudRepository<T, ID> extends Repository<T, ID> {
Optional<T> get(ID id);
}
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
}
Kotlin:
class Entity {
override fun toString() = "datString"
}
interface EntityRepository : PagingAndSortingRepository<Entity, Int?> {
override fun get(id: Int?): Optional<Entity> // It's happy and compiles
}
However in my actual project it’s behaving differently
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Sort
import org.springframework.data.repository.PagingAndSortingRepository
import org.springframework.data.rest.core.annotation.RepositoryRestResource
import org.springframework.data.rest.core.annotation.RestResource
import entity.MyEntity
import java.util.*
@RepositoryRestResource(exported = true)
interface MyEntityRepository : PagingAndSortingRepository<MyEntity, Int?> {
...
@RestResource(exported = true)
override fun findById(id: Int?): Optional<MyEntity> // Error:(52, 5) Kotlin: 'findById' overrides nothing
}
The definition of findById
in Springs CrudRepository
is:
public interface CrudRepository<T, ID> extends Repository<T, ID> {
...
Optional<T> findById(ID id);
}
build.gradle
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:2.0.3.RELEASE")
}
}
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.2.51'
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
bootJar {
baseName = 'gs-accessing-data-rest'
version = '0.1.0'
}
repositories {
mavenCentral()
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {
compile "org.springframework.boot:spring-boot-starter-data-rest"
compile "org.springframework.boot:spring-boot-starter-data-jpa"
compile "com.h2database:h2"
compile "org.jetbrains.kotlin:kotlin-reflect"
testCompile "org.springframework.boot:spring-boot-starter-test"
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
}
src/test/kotlin
package stuff
import org.junit.Test
import org.junit.runner.RunWith
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.data.repository.PagingAndSortingRepository
import org.springframework.data.rest.core.annotation.RepositoryRestResource
import org.springframework.data.rest.core.annotation.RestResource
import org.springframework.test.context.junit4.SpringRunner
import java.util.*
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id
@Entity
class Person {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private val id = 0
var firstName: String? = null
var lastName: String? = null
}
@RepositoryRestResource(collectionResourceRel = "people", path = "people")
interface PersonRepository : PagingAndSortingRepository<Person, Int> {
@RestResource(exported = true)
override fun findById(id: Int) = Optional.empty<Person>()
}
@SpringBootApplication
open class App
@RunWith(SpringRunner::class)
@SpringBootTest
class appTest {
@Test fun test() {}
}
just works
noo.blaster:
@RepositoryRestResource(collectionResourceRel = "people", path = "people")
interface PersonRepository : PagingAndSortingRepository<Person, Int> {
@RestResource(exported = true)
override fun findById(id: Int) = Optional.empty<Person>()
}
In your definition you’re using Int
as the ID type rather than Int?
; this ends up with java.lang.Interger
for the generics and primitive int
for the function arguments; this means the two don’t match and Spring can’t find the methods.
i.e. it looks for a findById
method accepting a type that matches the ID type (java.lang.Integer
) but can only find findById(int id)
.