Skip to content

Kotlin: Invoked method is not a property accessor exception when using method name query derivation and switching from Spring Boot 3.2.8 to version 3.2.9 #3771

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
mmrsic opened this issue Feb 11, 2025 · 2 comments
Labels
status: invalid An issue that we don't feel is valid

Comments

@mmrsic
Copy link

mmrsic commented Feb 11, 2025

Please investigate whether the following issue has a known workaround that I could use to mitigate it and/or whether it will be fixed in a follwoing 3.2.x version.

Issue

When accessing a JPA Spring Data repository with automatic code generation, the Spring Boot 3.2.9 version raises an IllegalArgumentException: Invoked method is not a property accessor".
Changing soleley the Spring Boot version from 3.2.9 back to 3.2.8 fixes the issue. Using any 3.2.x version where x between 9 and 12 reproduces the exception.

Stacktrace:

Invoked method is not a property accessor
java.lang.IllegalStateException: Invoked method is not a property accessor
	at org.springframework.data.projection.PropertyAccessingMethodInterceptor.invoke(PropertyAccessingMethodInterceptor.java:66)
	at org.springframework.data.projection.ProjectingMethodInterceptor.invoke(ProjectingMethodInterceptor.java:71)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
	at org.springframework.data.projection.ProxyProjectionFactory$TargetAwareMethodInterceptor.invoke(ProxyProjectionFactory.java:243)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223)
	at jdk.proxy3/jdk.proxy3.$Proxy147.getChildId(Unknown Source)
	at org.springboot.issues.springbootdata329issue.ApplicationTests.testInvokedMethodIsNotAPropertyAccessorSpringBoot329(ApplicationTests.kt:26)

Test case to reproduce

import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest


@DataJpaTest
class ApplicationTests(@Autowired val repo: SpringBootTestRepository) {

    /**
     * This test case works when using Spring Boot 3.2.8 but fails when using version 3.2.9.
     */
    @Test
    fun testInvokedMethodIsNotAPropertyAccessorSpringBoot329() {
        val childId = "childId"
        val child = SpringBootTestChildEntity(childId)
        val parent = SpringBootTestParentEntity(parentId = "parentId", children = mutableListOf(child))
        repo.save(parent)

        // act
        val result = repo.findByChildrenChildId(childId) // children is the parent entity's attribute, and childId is the child entity's attribute, see JPA classes below.

        // assert
        assert(result.isNotEmpty())
        result.forEach {
            val children = it.children
            children.childId // This fails with IllegalStateException for Spring Boot 3.2.9
        }
    }
}

JPA Repository

import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository

@Repository
interface SpringBootTestRepository : JpaRepository<SpringBootTestParentEntity, String> {

    fun findByChildrenChildId(wantedChildId: String): List<SpringBootTestParentInterface>
}

interface SpringBootTestParentInterface {

    val parentId: String
    val children: SpringBootTestChildInterface

}

interface SpringBootTestChildInterface {

    val childId: String
}

Entities

import jakarta.persistence.*

@Entity(name = "ParentEntity")
@Table(name = "PARENT_TABLE")
open class SpringBootTestParentEntity() {

    private var parentId: String? = null
    private var children: MutableList<SpringBootTestChildEntity> = ArrayList()

    constructor(parentId: String, children: MutableList<SpringBootTestChildEntity>) : this() {
        this.parentId = parentId
        this.children = children
    }

    @Id
    open fun getParentId() = parentId
    open fun setParentId(id: String) {
        parentId = id
    }

    @ElementCollection(targetClass = SpringBootTestChildEntity::class, fetch = FetchType.EAGER)
    @CollectionTable(
        name = "CHILD_TABLE",
        joinColumns = [JoinColumn(name = "parentId", referencedColumnName = "parentId")],
    )
    open fun getChildren() = children

    open fun setChildren(newChildren: MutableList<SpringBootTestChildEntity>) {
        children = newChildren
    }
}

@Embeddable
@Table(name = "CHILD_TABLE")
open class SpringBootTestChildEntity() {

    constructor(id: String) : this() {
        childId = id
    }

    private var childId: String = ""

    open fun getChildId() = childId

    open fun setChildId(newId: String) {
        childId = newId
    }
}

### Gradle
plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.9.25'
    id 'org.jetbrains.kotlin.plugin.spring' version '1.9.25'
    id 'org.springframework.boot' version '3.2.9'
    id 'io.spring.dependency-management' version '1.1.7'
    id 'org.jetbrains.kotlin.plugin.jpa' version '1.9.25'
}

group = 'org.springboot.issues'
version = '0.0.1-SNAPSHOT'

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.jetbrains.kotlin:kotlin-reflect'

    testImplementation 'com.h2database:h2'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.jetbrains.kotlin:kotlin-test-junit5'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

kotlin {
    compilerOptions {
        freeCompilerArgs.addAll '-Xjsr305=strict'
    }
}

allOpen {
    annotation 'jakarta.persistence.Entity'
    annotation 'jakarta.persistence.MappedSuperclass'
    annotation 'jakarta.persistence.Embeddable'
}

tasks.named('test') {
    useJUnitPlatform()
}

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Feb 11, 2025
@snicoll snicoll transferred this issue from spring-projects/spring-boot Feb 11, 2025
@mp911de
Copy link
Member

mp911de commented Feb 11, 2025

The issue has been introduced with spring-projects/spring-data-commons#3127 in a service release and was fixed with spring-projects/spring-data-commons#3215.

Spring Data JPA 3.2 entered OSS EOL in November 2024. Please upgrade to Spring Data JPA 3.3.8 or 3.4.x.

@mp911de mp911de closed this as completed Feb 11, 2025
@mp911de mp911de added status: invalid An issue that we don't feel is valid and removed status: waiting-for-triage An issue we've not yet triaged labels Feb 11, 2025
@mmrsic
Copy link
Author

mmrsic commented Feb 13, 2025

There's still another more specific issue present in current version 3.3.8: When using a Boolean property without "is" prefix, an exception is raised in version 3.3.8 whereas no exception is raised in version 3.2.8: "Invoked method is not a property accessor"

Kindly check out the extended test case showing two Boolean variants: https://github.com/mmrsic/spring-boot-data-329-issue/blob/main/src/test/kotlin/org/springboot/issues/springbootdata329issue/ApplicationTests.kt

The test case works in 3.2.8 but fails in 3.3.8 and 3.2.9-12.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: invalid An issue that we don't feel is valid
Projects
None yet
Development

No branches or pull requests

3 participants