Kotlin with JPA: default constructor hell

Solution 1:

As of Kotlin 1.0.6, the kotlin-noarg compiler plugin generates synthetic default construtors for classes that have been annotated with selected annotations.

If you use gradle, applying the kotlin-jpa plugin is enough to generate default constructors for classes annotated with @Entity:

buildscript {
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version"
    }
}

apply plugin: "kotlin-jpa"

For Maven:

<plugin>
    <artifactId>kotlin-maven-plugin</artifactId>
    <groupId>org.jetbrains.kotlin</groupId>
    <version>${kotlin.version}</version>

    <configuration>
        <compilerPlugins>
            <plugin>jpa</plugin>
        </compilerPlugins>
    </configuration>

    <dependencies>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-noarg</artifactId>
            <version>${kotlin.version}</version>
        </dependency>
    </dependencies>
</plugin>

Solution 2:

just provide default values for all arguments, Kotlin will make default constructor for you.

@Entity
data class Person(val name: String="", val age: Int=0)

see the NOTE box below the following section:

https://kotlinlang.org/docs/reference/classes.html#secondary-constructors

Solution 3:

@D3xter has a good answer for one model, the other is a newer feature in Kotlin called lateinit:

class Entity() {
    constructor(name: String, age: Date): this() {
        this.name = name
        this.birthdate = age
    }

    lateinit var name: String
    lateinit var birthdate: Date
}

You would use this when you are sure something will fill in the values at construction time or very soon after (and before first use of the instance).

You will note I changed age to birthdate because you cannot use primitive values with lateinit and they also for the moment must be var (restriction might be released in the future).

So not a perfect answer for immutability, same problem as the other answer in that regard. The solution for that is plugins to libraries that can handle understanding the Kotlin constructor and mapping properties to constructor parameters, instead of requiring a default constructor. The Kotlin module for Jackson does this, so it is clearly possible.

See also: https://stackoverflow.com/a/34624907/3679676 for exploration of similar options.

Solution 4:

Adding the JPA plugin in gradle worked for me:

plugins {
   id("org.springframework.boot") version "2.3.4.RELEASE"
   id("io.spring.dependency-management") version "1.0.10.RELEASE"
   kotlin("jvm") version "1.3.72"
   kotlin("plugin.spring") version "1.3.72"
   kotlin("plugin.jpa") version "1.3.72"
}

Solution 5:

@Entity data class Person(/*@Id @GeneratedValue var id: Long? = null,*/
                          var name: String? = null,
                          var age: Int? = null)

Initial values are requires if you want reuse constructor for different fields, kotlin doesn't allowed nulls. So whenever you planning omit field, use this form in constructor: var field: Type? = defaultValue

jpa required no argument constructor:

val entity = Person() // Person(name=null, age=null)

there is no code duplication. If you need construct entity and only setup age, use this form:

val entity = Person(age = 33) // Person(name=null, age=33)

there is no magic (just read documentation)