Title: Inconsistent Proxy Behavior with Kotlin Property Access and Custom Method

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.

For my investigation, I think reasons that this inconsistent error happen are from allowing to creating function with same name, but different returning signature like the below code.

fun getAdProductId(): java.lang.Long {

}

fun getAdProductId(): long {

}

So, I was trying to type same code with Java. But, Java didn’t allow to type these code. The errors came from java was “‘getAdProductId()’ clashes with ‘getAdProductId()’; both methods have same erasure”. Thus, this problem can cause this inconsistent proxy behavior.

The below response is from kotlin-lang slack channel.

This is not a platform declaration clash because these declarations are technically not clashing from the Kotlin's point of view.
There's a request at KT-23913 to improve behavior in such cases though. Personally I believe it could be a warning or an error in this special "Java API" mode.
YouTrackYouTrack
Add compilation mode for Java API developers : KT-23913
When it's enabled warnings should be reported whenever resulting API becomes problematic for usage from Java (while being fine in Kotlin) See related issues for examples

@tmdgusya this already looks like an edge-case on the Kotlin side, and I would certainly discourage having properties with multiple getter methods on Hibernate entities in the first place. We can help you if you can demonstrate a problem that occurs with a simple Java application, but keep in mind that property accessor methods for entities in the JPA specification must follow the Java Beans conventions, and while Hibernate tries to be more lax about this requirement it’s still heavily encouraged.

1 Like