Question
Kotlin and JPA Entities: Default Constructors, Immutability, and Practical Patterns
Question
In JPA, @Entity classes must provide a no-argument constructor so the framework can create objects when loading them from the database.
In Kotlin, it is very natural to define properties in the primary constructor, for example:
class Person(val name: String, val age: Int)
However, if you add the required no-argument constructor as a secondary constructor, you must still pass values to the primary constructor:
@Entity
class Person(val name: String, val age: Int) {
private constructor() : this("", 0)
}
This becomes awkward when the properties are complex, non-nullable types rather than simple values like String or Int. Supplying placeholder values can make the code harder to read and maintain. It is especially problematic if the primary constructor or init blocks contain real logic, because that logic will run during construction even though JPA may later replace property values through reflection.
Also, val properties cannot be reassigned after construction, so using JPA in this way appears to conflict with immutability.
How should Kotlin code be designed to work well with JPA without duplicating code, inventing "magic" default values, or giving up immutability unnecessarily?
Also, is it true that Hibernate, beyond the JPA specification itself, can instantiate objects even if they do not have a default constructor?
Short Answer
By the end of this page, you will understand why JPA expects entity classes to have a no-argument constructor, why this clashes with idiomatic Kotlin constructor-based design, and what patterns Kotlin developers commonly use in real projects. You will also learn when val properties are problematic in JPA entities, how Hibernate differs from the JPA spec, and how Kotlin compiler plugins can remove much of the boilerplate.
Concept
JPA was designed around Java-style classes that frameworks can instantiate and then populate. Because of that, JPA expects entity classes to have a no-argument constructor that is at least protected or public.
Kotlin encourages a different style:
- properties in the primary constructor
- non-null types
valfor immutability- initialization logic close to the constructor
These are excellent language features, but JPA entity management works differently.
Why JPA wants a no-arg constructor
When JPA loads data from the database, it needs a way to create an empty object instance first. After that, it fills in the fields. A no-argument constructor gives the framework a reliable way to do that.
Why this feels awkward in Kotlin
In Kotlin, entity classes are often written like regular domain classes:
class Person(val name: String, val age: Int)
But JPA does not naturally work with that style because:
- it may create the object before values are available
- it may inject values after construction
- proxies and lazy loading often require classes and properties to be mutable or overridable
The real mismatch
The core issue is not just the constructor. It is that:
- Kotlin favors constructor-initialized, immutable objects
- JPA favors framework-managed, partially constructed, mutable objects
That means JPA entities are often not the best place to enforce full immutability.
What developers usually do
Common Kotlin + JPA solutions include:
- using the Kotlin
jpaorno-argcompiler plugin to generate a synthetic no-arg constructor - making entity properties
varinstead ofval - avoiding business logic in entity constructors and
initblocks - keeping entities simple and using separate domain models or DTOs when stronger immutability is needed
About Hibernate specifically
Hibernate is more flexible than the JPA specification in some areas, but relying on provider-specific behavior can lock your code to Hibernate. In practice, if you are writing JPA entities, you should still design them to satisfy JPA requirements:
- no-arg constructor
- entity classes that Hibernate/JPA can instantiate
- cautious use of final classes and final properties
So yes, Hibernate has some internal capabilities beyond pure JPA, but the safe and portable approach is still to follow JPA entity rules.
Mental Model
Think of a JPA entity like a house being prepared by a moving company.
In idiomatic Kotlin, you usually want to build the whole house fully furnished from the start:
- all required values supplied in the constructor
- everything fixed in place
- immutable where possible
JPA works more like this:
- it first creates an empty house shell
- then it moves furniture into the rooms
- sometimes it leaves some rooms as placeholders for lazy loading
That means JPA needs a class it can create first and fill later.
So a JPA entity is less like a perfectly locked immutable value object, and more like a framework-managed container whose state is assembled over time.
If you need true immutability, it is often better to map the entity to a separate domain object after loading.
Syntax and Examples
A common Kotlin + JPA entity style looks like this:
import jakarta.persistence.Entity
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id
@Entity
class Person(
var name: String,
var age: Int
) {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null
protected constructor() : this("", 0)
}
What this example shows
@Entitymarks the class as a JPA entity- JPA can use the no-arg constructor
- properties are
varso the framework can set them idis nullable because it may not exist before persistence
This works, but the placeholder values are not ideal.
Better approach: use the Kotlin JPA plugin
A more idiomatic approach is to let the compiler generate the no-arg constructor for entity classes.
For Gradle Kotlin DSL:
plugins {
kotlin("jvm") version "1.9.0"
kotlin("plugin.jpa") version "1.9.0"
}
Then you can write:
import jakarta.persistence.Entity
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id
@Entity
class Person(
var name: String,
var age: Int
) {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null
}
Why this is better
- no fake constructor arguments in your code
- less duplication
- cleaner entity definitions
Important note about val
You may be tempted to write:
@Entity
class Person(
val name: String,
val age: Int
)
This is usually problematic for JPA entities because the persistence provider may need to set fields after construction. In practice, var is usually the safer choice for entity state.
Better separation: entity vs domain model
A very common real-world pattern is:
@Entity
class PersonEntity(
var name: String,
var age: Int
) {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null
}
data class Person(
val id: Long?,
val name: String,
val age: Int
)
Then map between them:
fun PersonEntity.toDomain(): Person = Person(
id = id,
name = name,
age = age
)
This lets your domain model stay immutable even if the JPA entity cannot be.
Step by Step Execution
Consider this entity:
@Entity
class Person(
var name: String,
var age: Int
) {
@Id
var id: Long? = null
protected constructor() : this("", 0)
}
Now imagine JPA loads one row from the database.
Step-by-step
1. JPA creates the object
It calls the no-argument constructor:
Person()
At this point, the object temporarily contains:
name = ""age = 0id = null
2. JPA populates fields
Using reflection or its internal access strategy, JPA sets the real values from the database row.
For example, after loading:
name = "Alice"age = 30id = 5
3. Your application receives the entity
Now the entity reflects the database state.
Why constructor logic can be dangerous
Suppose you do this:
@Entity
class Person(
var name: String,
var age: Int
) {
init {
require(name.isNotBlank())
require(age >= 0)
}
protected constructor() : this("", 0)
}
The no-arg constructor calls this("", 0), so the init block runs immediately and fails because name is blank.
That is why heavy validation or business logic in entity constructors often causes trouble with JPA.
Safer idea
Keep entity construction simple, and put validation in:
- service layer logic
- factory methods
- separate domain objects
- request validation before persistence
Real World Use Cases
This concept appears in many real applications.
Database-backed web applications
In a Spring Boot app, entities represent database tables. JPA loads records by instantiating entity objects internally, so a no-arg constructor is needed.
Admin panels and CRUD systems
When users edit rows such as products, users, or orders, JPA reads and updates entity instances repeatedly. Mutable entity fields make this practical.
Lazy loading of relationships
An entity may have related objects such as:
Order->CustomerPost->AuthorInvoice->LineItems
JPA providers often use proxies or delayed initialization, which fits better with framework-managed mutable entities than with fully immutable constructor-only objects.
Domain model separation
In larger codebases, teams often use:
- JPA entities for persistence
- immutable domain models for business logic
- DTOs for API input/output
This avoids forcing one class to solve persistence, validation, and API concerns at the same time.
Real Codebase Usage
In real projects, developers rarely put complex constructor logic directly in JPA entities.
Common patterns
1. Simple entity, richer service layer
Entities are kept lightweight:
@Entity
class Account(
var email: String,
var status: String
) {
@Id
@GeneratedValue
var id: Long? = null
protected constructor() : this("", "NEW")
}
Validation and rules happen in services:
fun createAccount(email: String): Account {
require(email.contains("@"))
return Account(email, "NEW")
}
2. Guard clauses outside the entity
Instead of relying on constructor validation, applications validate inputs before creating or saving entities.
3. Entity-to-domain mapping
Persistence model:
@Entity
class UserEntity(
var username: String,
var active: Boolean
) {
@Id
@GeneratedValue
var id: Long? = null
}
Domain model:
data class User(
val id: Long?,
val username: String,
val active: Boolean
)
4. Compiler plugins for cleaner code
Many Kotlin + JPA projects enable:
kotlin("plugin.jpa")kotlin("plugin.allopen")
These help with:
- generated no-arg constructors
- making classes open where JPA/Hibernate need proxies
5. Nullable IDs, mutable persistent state
A common real-world rule is:
- database-generated IDs are nullable before insert
- persistent properties are often
var - entity invariants are enforced carefully, not only in constructors
Common Mistakes
1. Using val for fields that JPA must populate
Broken or risky example:
@Entity
class Person(
val name: String,
val age: Int
)
Why it is a problem:
- JPA may need to assign values after construction
- immutable properties do not fit many provider behaviors
Safer approach:
@Entity
class Person(
var name: String,
var age: Int
) {
protected constructor() : this("", 0)
}
2. Putting validation in init that rejects placeholder construction
Broken example:
@Entity
class Person(
var name: String
) {
init {
require(name.isNotBlank())
}
protected constructor() : this("")
}
Why it fails:
- the secondary constructor still calls the primary constructor
- the validation runs before JPA injects real values
Avoid it by:
- moving validation elsewhere
- using separate domain objects
3. Relying on fake magic values everywhere
Example:
protected constructor() : this("UNKNOWN", -1)
Why it is bad:
- values may leak into logic accidentally
- they hide the real lifecycle of the entity
- they make code harder to trust
Use compiler plugins or separate persistence/domain models instead.
4. Assuming JPA entities should look like Kotlin data classes
Data classes are great for immutable value objects, but JPA entities often need:
- mutable properties
- lifecycle management
- proxy support
- identity separate from value equality
So entities usually should not be modeled exactly like regular immutable data classes.
5. Ignoring provider and proxy requirements
Some JPA providers, especially with lazy loading, work better when classes and members are not final. Kotlin classes are final by default.
Use the proper plugin setup when needed.
Comparisons
| Approach | Pros | Cons | Best use |
|---|---|---|---|
| Secondary no-arg constructor with placeholder values | Simple, explicit, no plugin required | Boilerplate, fake values, constructor logic may misbehave | Small demos or simple entities |
Kotlin jpa/no-arg plugin | Clean entity code, less duplication | Requires build configuration | Most Kotlin + JPA projects |
val constructor properties | Immutable style, concise | Often conflicts with JPA population and proxies | Domain models, DTOs, value objects |
var entity properties | Works well with JPA lifecycle | Less strict immutability | JPA entities |
| Separate entity and domain model | Clear boundaries, immutable domain possible | Requires mapping code | Medium and large applications |
JPA entity vs immutable domain object
| Aspect | JPA Entity | Immutable Domain Object |
|---|---|---|
| Construction | Often framework-created | App-created directly |
| Mutability | Usually mutable | Usually immutable |
| No-arg constructor | Often needed | Usually not needed |
| Best for | Persistence | Business logic |
| Validation | Limited in constructor | Stronger constructor/factory validation |
JPA spec vs Hibernate-specific behavior
| Topic | JPA | Hibernate |
|---|---|---|
| No-arg constructor | Expected for entities | Generally should still be provided |
| Portability | High | Lower if using special behavior |
| Best practice | Follow spec rules | Use extra features carefully |
Cheat Sheet
Quick rules for Kotlin + JPA entities
- JPA entities should have a no-arg constructor.
- In Kotlin, use the
kotlin("plugin.jpa")plugin when possible. - Prefer
varovervalfor entity properties. - Avoid complex logic in entity constructors and
initblocks. - Keep entity classes simple and persistence-focused.
- Use separate domain models if you want strong immutability.
- IDs are often nullable before persistence.
- Be aware that Kotlin classes are final by default; JPA/Hibernate may need open classes for proxies.
Typical entity shape
@Entity
class Product(
var name: String,
var price: BigDecimal
) {
@Id
@GeneratedValue
var id: Long? = null
protected constructor() : this(BigDecimal.ZERO.toString(), BigDecimal.ZERO)
}
Better with plugin
@Entity
class Product(
var name: String,
var price: BigDecimal
) {
@Id
@GeneratedValue
var id: Long? = null
}
Red flags
initblock with strict validation in an entity- many fake constructor defaults
- using data classes as JPA entities by default
- assuming Hibernate-specific behavior is portable
Good pattern
data class Product(
val id: Long?,
val name: String,
val price: BigDecimal
)
@Entity
class ProductEntity(
var name: String,
var price: BigDecimal
) {
@Id
@GeneratedValue
var id: Long? = null
}
FAQ
Why does JPA require a no-argument constructor?
JPA needs a simple way to instantiate entity objects before populating their fields from database data.
Can I use val properties in Kotlin JPA entities?
Sometimes it may compile, but it is usually not a good fit. JPA often expects entity state to be assignable after construction, so var is safer.
Is the Kotlin JPA plugin the recommended solution?
Yes, in many Kotlin + JPA projects it is the cleanest way to avoid writing boilerplate no-arg constructors manually.
Should I put validation in an entity constructor?
Usually only very carefully. Constructor and init logic can conflict with how JPA creates and hydrates entities.
Are Kotlin data classes good JPA entities?
Usually not by default. Data classes are designed for value semantics, while JPA entities have identity, lifecycle, and proxy concerns.
Can Hibernate instantiate entities without a default constructor?
Hibernate has provider-specific internals and may support more than plain JPA in some cases, but relying on that reduces portability. The safer practice is still to provide what JPA expects.
How do I keep immutability if JPA entities are mutable?
A common solution is to keep entities mutable for persistence and convert them to immutable domain models or DTOs in the service layer.
Mini Project
Description
Build a small persistence model for a user registry using Kotlin and JPA. The project demonstrates how to define a JPA entity that works with framework requirements while keeping your business-facing model separate and immutable. This mirrors a common real-world pattern used in backend services.
Goal
Create a mutable JPA entity and map it to an immutable domain model without using constructor validation that breaks JPA loading.
Requirements
- Create a
UserEntityJPA class withid,username, andemailfields. - Make the entity compatible with JPA construction requirements.
- Create a separate immutable
Userdomain model. - Write mapping functions between
UserEntityandUser. - Add a simple factory function that validates input before creating the entity.
Keep learning
Related questions
Accessing Kotlin Extension Functions from Java
Learn how Kotlin extension functions are compiled and how to call them correctly from Java with clear examples and common pitfalls.
Add a Maven Repository URL in Gradle Kotlin DSL (build.gradle.kts)
Learn how to add Maven Central and a custom Maven repository URL in Gradle Kotlin DSL using build.gradle.kts syntax.
Allow HTTP and HTTPS in Android 9 Pie with Network Security Configuration
Learn how Android 9 Pie handles cleartext HTTP traffic and how to allow HTTP and HTTPS safely using network security config.