Type parameter of Int? is treated as java.lang.Integer for generics and int for function parameters

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 :stuck_out_tongue_winking_eye: )… 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 :expressionless:

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 :frowning:

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

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).