Description:
I am experiencing inconsistent behavior with Hibernate proxies when accessing a Kotlin property that has a custom getter method with the same name but a different return type.
Steps to Reproduce:
@Entity
class AdGroup(
@Id
...
@Column
var adProcutId: Long? = null
) {
fun getAdProductId(): Long = requiredNotNull(this.adProcutId)
}
- Create a Kotlin entity class with a property named adProductId of type Long? (nullable).
- Define a custom getter method named getAdProductId() in the entity class, which returns Long.
Access the adProductId property using adGroup.adProductId.
Expected Behavior:
Hibernate proxy should intercept the property access and call the long-returning getAdProductId() method.
Actual Behavior:
The behavior is inconsistent across different deployments.
In some deployments, the proxy correctly intercepts the property access and calls the long-returning getAdProductId() method.
In other deployments, the proxy bypasses the interceptor and directly accesses the adProductId field, resulting in a null value.
// Debug Output Examples
// Case 1: When it fails (adProductId is null but getAdProductId() returns 3)
println("${adGroup.adProductId} || ${adGroup.getAdProductId()}")
// Output: null || 3
// Case 2: When it succeeds (both return 3, no code changes, only redeployed)
println("${adGroup.adProductId} || ${adGroup.getAdProductId()}")
// Output: 3 || 3
Analysis:
The issue seems to be related to the order of methods in the publicMethods array of the entity class’s reflection data.
When the long-returning getAdProductId() method appears before the Long-returning (Kotlin-generated getter) getAdProductId() method in the publicMethods array, the proxy behaves correctly.
When the Long-returning getAdProductId() method appears first, the proxy bypasses the interceptor and accesses the field directly.
debug data when it succeed
persistentClass = class org.xx.xx.AdGroup
42 = {Method@27931} "public long org.xx.xx.getAdProductId()"
methodAccessor = {DelegatingMethodAccessorImpl@27995}
method = {Method@27931} "public long org.xx.xx.getAdProductId()"
delegate = {NativeMethodAccessorImpl@27998}
43 = {Method@27932} "public java.lang.Long org.xx.xx.getAdProductId()"
methodAccessor = null
name = "getAdProductId"
returnType = {Class@499} "class java.lang.Long"\
debug data when it failed
46 = {Method@27714} "public java.lang.Long org.xx.xx.getAdProductId()"
methodAccessor = {DelegatingMethodAccessorImpl@27773}
delegate = {NativeMethodAccessorImpl@27777}
method = {Method@27714} "public java.lang.Long org.xx.xx.getAdProductId()"
47 = {Method@27715} "public long org.xx.xx.getAdProductId()"
methodAccessor = null
Solution (I found it):
Rename the custom getter method to avoid name conflict with the Kotlin property getter.
Add the @Transient annotation to the Long-returning getAdProductId() method.
Java Version: JDK 11
Kotlin version: org.jetbrains.kotlin:kotlin-stdlib-jdk8
Hibernate version: 5.6.15.Final
Request:
Please investigate this issue. It would be helpful to understand why the proxy behavior is affected by the method order in the publicMethods array and whether there is a more reliable way to ensure consistent proxy behavior.