Question

Kotlin and JPA Entities: Default Constructors, Immutability, and Practical Patterns

kotlinhibernatejpadefault-constructor

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
  • val for 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 jpa or no-arg compiler plugin to generate a synthetic no-arg constructor
  • making entity properties var instead of val
  • avoiding business logic in entity constructors and init blocks
  • 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:

  1. it first creates an empty house shell
  2. then it moves furniture into the rooms
  3. 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.

Take Quiz

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

  • @Entity marks the class as a JPA entity
  • JPA can use the no-arg constructor
  • properties are var so the framework can set them
  • id is 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 = 0
  • id = 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 = 30
  • id = 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 -> Customer
  • Post -> Author
  • Invoice -> 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.

Take Quiz

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

ApproachProsConsBest use
Secondary no-arg constructor with placeholder valuesSimple, explicit, no plugin requiredBoilerplate, fake values, constructor logic may misbehaveSmall demos or simple entities
Kotlin jpa/no-arg pluginClean entity code, less duplicationRequires build configurationMost Kotlin + JPA projects
val constructor propertiesImmutable style, conciseOften conflicts with JPA population and proxiesDomain models, DTOs, value objects
var entity propertiesWorks well with JPA lifecycleLess strict immutabilityJPA entities
Separate entity and domain modelClear boundaries, immutable domain possibleRequires mapping codeMedium and large applications

JPA entity vs immutable domain object

AspectJPA EntityImmutable Domain Object
ConstructionOften framework-createdApp-created directly
MutabilityUsually mutableUsually immutable
No-arg constructorOften neededUsually not needed
Best forPersistenceBusiness logic
ValidationLimited in constructorStronger constructor/factory validation

JPA spec vs Hibernate-specific behavior

TopicJPAHibernate
No-arg constructorExpected for entitiesGenerally should still be provided
PortabilityHighLower if using special behavior
Best practiceFollow spec rulesUse extra features carefully
Take Quiz

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 var over val for entity properties.
  • Avoid complex logic in entity constructors and init blocks.
  • 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

  • init block 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.

Take Quiz

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 UserEntity JPA class with id, username, and email fields.
  • Make the entity compatible with JPA construction requirements.
  • Create a separate immutable User domain model.
  • Write mapping functions between UserEntity and User.
  • Add a simple factory function that validates input before creating the entity.
Take Quiz

Keep learning

Related questions

View more