Hibernate / JPA Conversion Guide
When This Applies
Detected when imports match any of:
javax.persistence.*jakarta.persistence.*org.hibernate.*
Critical Rules
Do NOT use data classes for JPA entities. Data classes generate
equals/hashCodebased on all properties, which breaks Hibernate's identity semantics and proxy creation.Keep entity classes
open. Hibernate creates proxies via subclassing. Kotlin classes arefinalby default, so you must useopenexplicitly (or use theallopencompiler plugin with JPA annotation support).Provide a no-argument constructor if Hibernate requires one for proxy creation. Use a secondary constructor or default values for all primary constructor parameters.
Annotation site targets matter:
@Id,@Column,@GeneratedValueon fields → use@field:Id,@field:Column, etc. in Kotlin, OR place annotations on constructor parameters with@field:site target.@ManyToOne,@OneToMany,@JoinColumn→ same@field:targeting.
Lazy loading considerations:
@ManyToOne(fetch = FetchType.LAZY)requires the entity class to be open for proxy creation.@OneToManywith lazy collections work with Kotlin'sMutableList.@Embeddableclasses: Can be data classes (they don't need proxies).@MappedSuperclass: Must beopen abstract classin Kotlin.
Examples
Example 1: JPA Entity with @Id, @Column, and Relationships
Java:
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "username", nullable = false, unique = true)
private String username;
@Column(name = "email")
private String email;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "department_id")
private Department department;
protected User() {}
public User(String username, String email, Department department) {
this.username = username;
this.email = email;
this.department = department;
}
public Long getId() { return id; }
public String getUsername() { return username; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public Department getDepartment() { return department; }
public void setDepartment(Department department) { this.department = department; }
}Kotlin:
@Entity
@Table(name = "users")
open class User(
@field:Column(name = "username", nullable = false, unique = true)
open val username: String,
@field:Column(name = "email")
open var email: String? = null,
@field:ManyToOne(fetch = FetchType.LAZY)
@field:JoinColumn(name = "department_id")
open var department: Department? = null
) {
@field:Id
@field:GeneratedValue(strategy = GenerationType.IDENTITY)
open var id: Long? = null
protected set
protected constructor() : this(username = "")
}Example 2: @Embeddable Value Object
Java:
@Embeddable
public class Address {
@Column(name = "street")
private String street;
@Column(name = "city")
private String city;
@Column(name = "zip_code")
private String zipCode;
protected Address() {}
public Address(String street, String city, String zipCode) {
this.street = street;
this.city = city;
this.zipCode = zipCode;
}
public String getStreet() { return street; }
public String getCity() { return city; }
public String getZipCode() { return zipCode; }
}Kotlin:
@Embeddable
data class Address(
@field:Column(name = "street")
val street: String = "",
@field:Column(name = "city")
val city: String = "",
@field:Column(name = "zip_code")
val zipCode: String = ""
)@Embeddable classes can safely be data classes because Hibernate does not proxy them.
Default values satisfy the no-arg constructor requirement.
Example 3: Entity with @ManyToOne and @OneToMany
Java:
@Entity
@Table(name = "departments")
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name", nullable = false)
private String name;
@OneToMany(mappedBy = "department", cascade = CascadeType.ALL, orphanRemoval = true)
private List<User> users = new ArrayList<>();
protected Department() {}
public Department(String name) {
this.name = name;
}
public Long getId() { return id; }
public String getName() { return name; }
public List<User> getUsers() { return users; }
public void addUser(User user) {
users.add(user);
user.setDepartment(this);
}
public void removeUser(User user) {
users.remove(user);
user.setDepartment(null);
}
}Kotlin:
@Entity
@Table(name = "departments")
open class Department(
@field:Column(name = "name", nullable = false)
open val name: String = ""
) {
@field:Id
@field:GeneratedValue(strategy = GenerationType.IDENTITY)
open var id: Long? = null
protected set
@field:OneToMany(mappedBy = "department", cascade = [CascadeType.ALL], orphanRemoval = true)
open val users: MutableList<User> = mutableListOf()
protected constructor() : this(name = "")
fun addUser(user: User) {
users.add(user)
user.department = this
}
fun removeUser(user: User) {
users.remove(user)
user.department = null
}
}Key points in this example:
cascadearray syntax uses Kotlin's[CascadeType.ALL]instead of Java's{CascadeType.ALL}.- The collection is typed as
MutableListto allow Hibernate to manage the relationship. - The class and its properties are
openso Hibernate can create proxies. - The no-arg constructor delegates to the primary constructor with default values.