Skip to content

BeanUtils.getPropertyDescriptors(…) for Kotlin class with Java superclass does not include properties from its superclass #2994

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
cmdjulian opened this issue Nov 27, 2023 · 6 comments
Labels
type: bug A general bug

Comments

@cmdjulian
Copy link

After updating to Spring Boot 3.2, my Kotlin app does not start anymore with an exception of Caused by: org.springframework.beans.NotWritablePropertyException: Invalid property 'queryLookupStrategyKey' of bean class [com.example.demo.JpaRepositoryFactoryBeanImpl]: Bean property 'queryLookupStrategyKey' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?.

After debugging for some time I found out that in 3.2 there seems to be some new property introspection for kotlin classes and their properties are active. The problem seems to be, that the setters from the super class, in that case JpaRepositoryFactoryBean are not recognized as they are not Kotlin properties.

I can work around that by including the following code:

private var queryLookupStrategyKey: Key
    get() = throw UnsupportedOperationException()
    @JvmName("setQueryLookupStrategyKeyKt") set(value) {
        super.setQueryLookupStrategyKey(value)
    }
private var lazyInit: Boolean
    get() = throw UnsupportedOperationException()
    @JvmName("setLazyInitKt") set(value) {
        super.setLazyInit(value)
    }
private var namedQueries: NamedQueries
    get() = throw UnsupportedOperationException()
    @JvmName("namedQueriesKt") set(value) {
        super.setNamedQueries(value)
    }
private var repositoryFragments: RepositoryFragments
    get() = throw UnsupportedOperationException()
    @JvmName("setRepositoryFragmentsKt") set(value) {
        super.setRepositoryFragments(value)
    }
private var transactionManager: String
    get() = throw UnsupportedOperationException()
    @JvmName("setTransactionManagerKt") set(value) {
        super.setTransactionManager(value)
    }
private var entityManager: EntityManager
    get() = throw UnsupportedOperationException()
    @JvmName("setEntityManagerKt") set(value) {
        super.setEntityManager(value)
    }
private var escapeCharacter: Char
    get() = throw UnsupportedOperationException()
    @JvmName("setEscapeCharacterKt") set(value) {
        super.setEscapeCharacter(value)
    }
private var mappingContext: MappingContext<*, *>
    get() = throw UnsupportedOperationException()
    @JvmName("setMappingContextKt") set(value) {
        super.setMappingContext(value)
    }
private var enableDefaultTransactions: Boolean
    get() = throw UnsupportedOperationException()
    @JvmName("setEnableDefaultTransactionsKt") set(value) {
        super.setEnableDefaultTransactions(value)
    }

Another workaround is writing that class in java, but this defeats the purpose of using Kotlin in the first place.

Attached is a minimal reproducible example: kotlin-jpa-bug.zip

@cmdjulian cmdjulian changed the title JpaRepositoryFactoryBean written in Kotlin causes NotWritablePropertyException with Spring Boot 3.2 JpaRepositoryFactoryBean written in Kotlin causes NotWritablePropertyException with Spring Boot 3.2 Nov 27, 2023
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Nov 27, 2023
@mp911de
Copy link
Member

mp911de commented Nov 28, 2023

Did you notice that the JVM name is setQueryLookupStrategyKeyKt and not setQueryLookupStrategyKey?

Your class needs to adhere to Java beans standards by having getters and setters that adhere to that naming scheme.

@cmdjulian
Copy link
Author

Yes, that is on purpose. Otherwise I get an accidental override error from the kotlin side of things. The problem here is, that my kotlin class inherits from a java class and the detector does not recognize the setter as its not a Kotlin property, as it isn't. When looking at the demo project I provided in the previous post, it makes more sense. The demo project is without any methods to showcase the error. It just inherits from the Spring provided JpaRepositoryFactoryBean class. In 3.1.5 this worked without any problems.
The logic seems not right here, as it affecting all kotlin class which do inherit from a java class

@mp911de
Copy link
Member

mp911de commented Nov 28, 2023

In 3.2 we introduced KotlinBeanInfoFactory that uses Kotlin's reflection API to detect KProperty for Kotlin classes. It's likely that this updated scheme now reflects back into other beans as well.

@cmdjulian
Copy link
Author

cmdjulian commented Nov 28, 2023

Yes, thats probably the reason why when defining the properties myself it is able to infer them. Kotlin is normally able to infer such properties from java as well, when they conform to the java bean standard. In the case of JpaRepositoryFactoryBean just a setter is present, no getter. I think thats the reason why Kotlin reflect is not able to detect it as a valid property.

I guess this will happen more often for all kind of kotlin class which do extend java classes / interfaces.

@mp911de mp911de transferred this issue from spring-projects/spring-data-jpa Nov 29, 2023
@mp911de mp911de added type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged labels Nov 29, 2023
@mp911de mp911de added this to the 3.2.1 (2023.1.1) milestone Nov 29, 2023
@mp911de
Copy link
Member

mp911de commented Nov 29, 2023

Like you said, properties from a non-Kotlin superclass aren't taken into account by Kotlin reflection. We can add these ourselves by walking the type hierarchy and resolving properties from the superclass using the regular BeanInfo mechanism.

This adds back any inherited properties.

@mp911de mp911de changed the title JpaRepositoryFactoryBean written in Kotlin causes NotWritablePropertyException with Spring Boot 3.2 BeanUtils.getPropertyDescriptors(…) for Kotlin class with Java superclass does not include properties from its superclass Nov 29, 2023
mp911de added a commit that referenced this issue Nov 29, 2023
We now include properties from non-Kotlin supertypes if the supertype is not a Kotlin type and not Object.

Closes #2994
mp911de added a commit that referenced this issue Nov 29, 2023
We now include properties from non-Kotlin supertypes if the supertype is not a Kotlin type and not Object.

Closes #2994
mp911de added a commit that referenced this issue Nov 29, 2023
We now include properties from non-Kotlin supertypes if the supertype is not a Kotlin type and not Object.

Closes #2994
@mp911de
Copy link
Member

mp911de commented Nov 29, 2023

I transferred the ticket into Spring Data Commons, where the code for KotlinBeanInfoFactory resides.

That's also addressed now in 3.2.1-SNAPSHOT. Care to test your application against that build?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug A general bug
Projects
None yet
Development

No branches or pull requests

3 participants