Using Kotlin, Spring Data JPA with Hibernate. Consider following JPA entity:
@Entity
@Table(name = "applications")
class Application(
@Column(name = "name", nullable = false)
var name: String,
@Column(name = "version", nullable = false)
var version: String,
) {
@Id
@SequenceGenerator(name = "seq_gen_application_id", sequenceName = "seq_application_id")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_gen_application_id")
@Column(name = "id", unique = true, nullable = false)
var id: Long? = null
@Embedded
var/val k8s: K8s = K8s()
@Embeddable
class K8s {
@Column(name = "k8s_image")
var image: String? = null
}
}
From the language level perspective, the Application.k8s
property is never null. So accessing the k8s
property without any null-checks should be safe.
Using, for example, findAll()
function from a classic CrudRepository
:
@Repository
interface ApplicationRepository : CrudRepository<Application, Long>
I will get a list of Application
instances, where the non-null k8s
property is null
(if k8s.image
is null).
This will cause a NullPointerException
at runtime when you access the property k8s
even if it shouldn't (thanks to Kotlin's null-safety mechanism). So it is very confusing and can lead to unexpected bugs. It may be caused by some Hibernate interoperability issues I guess? Hibernate finds out that all fields in the embeddable class are null, so it sets it to null using reflection?
Solution 1:
Declare k8s
property (and other embedded entities) always as nullable, which will force additional null checks.
Solution 2:
@Embedded
var k8s: K8s = K8s()
get() = if (field == null) K8s() else field
This is also confusing since there is a null check to a non-nullable type. So it generates a compiler warning:
Condition 'field == null' is always 'false'
Solution 3: Declare a helper class with a dummy property that ensures that there is always something that is never null.
@Embeddable
@MappedSuperclass
open class NonNullEmbeddable {
@Formula("0")
private var internalNonNullProperty: Int = 0
}
Usage:
class K8s : NonNullEmbeddable() {
@Column(name = "k8s_image")
var image: String? = null
}
Any thoughts? Is it a bug or expected behavior?
I would understand if the Application
entity comes from a Java code (https://kotlinlang.org/docs/java-interop.html#null-safety-and-platform-types). But since the Application
is declared as Kotlin class, it feels just wrong.