192

As JPA requires, @Entity classes should have a default (non-arg) constructor to instantiate the objects when retrieving them from the database.

In Kotlin, properties are very convenient to declare within the primary constructor, as in the following example:

class Person(val name: String, val age: Int) { /* ... */ }

But when the non-arg constructor is declared as a secondary one it requires values for the primary constructor to be passed, so some valid values are needed for them, like here:

@Entity
class Person(val name: String, val age: Int) {
    private constructor(): this("", 0)
}

In case when the properties have some more complex type than just String and Int and they're non-nullable, it looks totally bad to provide the values for them, especially when there's much code in primary constructor and init blocks and when the parameters are actively used -- when they're to be reassigned through reflection most of the code is going to be executed again.

Moreover, val-properties cannot be reassigned after the constructor executes, so immutability is also lost.

So the question is: how can Kotlin code be adapted to work with JPA without code duplication, choosing "magic" initial values and loss of immutability?

P.S. Is it true that Hibernate aside of JPA can construct objects with no default constructor?

2
  • 2
    INFO -- org.hibernate.tuple.PojoInstantiator: HHH000182: No default (no-argument) constructor for class: Test (class must be instantiated by Interceptor) – so, yes, Hibernate can work without the default constructor. Feb 13, 2017 at 9:28
  • The way it does it is with setters - aka: Mutability. It instantiates the default constructor and then looks for setters. I want immutable objects. The only way that can be done is if hibernates starts looking at the constructor. There is an open ticket on this hibernate.atlassian.net/browse/HHH-9440 Feb 8, 2018 at 7:54

15 Answers 15

192

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>
12
  • 5
    Could you perhaps expand a bit on how this would be used within your kotlin code, even if it's a case of "your data class foo(bar: String) doesn't change". It'd just be nice to see a more complete example of how this fits into place. Thanks
    – thecoshman
    Feb 8, 2017 at 22:56
  • 7
    This is the blog post that introduced kotlin-noarg and kotlin-jpa with links detailing their purpose blog.jetbrains.com/kotlin/2016/12/kotlin-1-0-6-is-here May 12, 2017 at 15:32
  • 1
    And what about a primary key class like CustomerEntityPK, which is not an entity but needs a default constructor?
    – jannnik
    Dec 28, 2017 at 17:27
  • 7
    Not working for me. It only works if I make the constructor fields optionals. Which means that the plugin isn't working.
    – User
    Jun 6, 2019 at 8:08
  • 3
    @jannnik You can mark the primary key class with the @Embeddable attribute even if you otherwise don't need it. That way, it will be picked up by kotlin-jpa.
    – svick
    Oct 3, 2019 at 8:46
38

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

6
  • 39
    you obviously didn't read his question, else you would have seen the part where he states that default arguments are bad looking, especially for more complex objects. Not to mention, adding default values for something hides other issues.
    – snowe
    Dec 1, 2016 at 23:26
  • 1
    Why is it bad idea to provide the default values? Even when using Java's no args consturctor, default values are assigned to the fields (e.g. null to reference types). Dec 21, 2016 at 2:53
  • 3
    There are times for which you can not provide a sensible defaults. Take the given example of a person, you really should model it with a date of birth as that doesn't change (of course, exceptions apply somewhere somehow) but there is no sensible default to give to that. Hence form a pure code point of view, you must pass a DoB into the person constructor, thus ensuring you can never have a person that doesn't have a valid age. The problem is, the way JPA likes to work, it likes to make an object with a no-args constructor, then set everything.
    – thecoshman
    Feb 8, 2017 at 22:54
  • 1
    I think this is the right way to do that, this answer works in other cases that you don't use JPA or hibernate too. also it's the suggested way according to documents as mentioned in the answer. Jul 18, 2017 at 14:25
  • 2
    Also, you should not use data class with JPA: "don’t use data classes with val properties because JPA is not designed to work with immutable classes or the methods generated automatically by data classes." spring.io/guides/tutorials/spring-boot-kotlin/…
    – Stianhn
    Apr 6, 2020 at 10:44
27

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"
}
12

@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.

3
  • Worth note that lateinit and Delegates.notNull() are the same.
    – fasth
    Feb 9, 2016 at 11:09
  • 4
    similar but not the same. If Delegate is used, it changes what is seen for serialization of the actual field by Java (it sees the delegate class). Also, it is better to use lateinit when you have a well defined lifecycle guaranteeing initialization soon after construction, it is intended for those cases. Whereas delegate is more intended for "sometime before first use". Although technically they have similar behavior and protection, they aren't identical. Feb 9, 2016 at 16:57
  • If you need to use primitive values, the only thing I could think of would be to use "default values" when instantiating an object, and by that I mean using 0 and false for Ints and Booleans respectively. Not sure how that would affect framework code though Sep 20, 2019 at 14:02
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)

3
  • 2
    While this code snippet may solve the question, including an explanation really helps to improve the quality of your post. Remember that you are answering the question for readers in the future, and those people might not know the reasons for your code suggestion.
    – DimaSan
    Mar 25, 2017 at 21:38
  • @DimaSan, you are right, but that thread already have explanations in some posts... Apr 16, 2017 at 23:51
  • 1
    But your snippet is different and though may have a different description, anyway now it is much clearer.
    – DimaSan
    Apr 17, 2017 at 0:23
4

There is no way to keep immutability like this. Vals MUST be initialized when constructing the instance.

One way to do it without immutability is:

class Entity() {
    public constructor(name: String, age: Int): this() {        
        this.name = name
        this.age = age
    }

    public var name: String by Delegates.notNull()

    public var age: Int by Delegates.notNull()
}
3
  • So there's even no way to tell Hibernate to map columns to the constructor args? Well, may be, there's an ORM framework/library which doesn't require non-arg constructor? :)
    – hotkey
    Aug 16, 2015 at 22:49
  • Not sure about that, haven't worked with Hibernate for a long time. But it should be possible somehow to implement with named parameters.
    – D3xter
    Aug 17, 2015 at 15:39
  • I think hibernate could do this with a bit (not much) of work. In java 8 you can actually have you parameters named in the constructor and those could be mapped just like they are to fields now. Jun 4, 2017 at 5:36
4

I'm a nub myself but seems you have to explicit initializer and fallback to null value like this

@Entity
class Person(val name: String? = null, val age: Int? = null)
0
3

I have been working with Kotlin + JPA for quite a while and I have created my own idea how to write Entity classes.

I just slightly extend your initial idea. As you said we can create private argumentless constructor and provide default values for primitives, but when we try need to use another classes it gets a little messy. My idea is to create static STUB object for entity class that you currently writes e.g:

@Entity
data class TestEntity(
    val name: String,
    @Id @GeneratedValue val id: Int? = null
) {
    private constructor() : this("")

    companion object {
        val STUB = TestEntity()
    }
}

and when I have entity class that is related to TestEntity I can easily use stub I just have created. For example:

@Entity
data class RelatedEntity(
        val testEntity: TestEntity,
        @Id @GeneratedValue val id: Long? = null
) {
    private constructor() : this(TestEntity.STUB)

    companion object {
        val STUB = RelatedEntity()
    }
}

Of course this solution is not perfect. You still need to create some boilerplate code that should not be required. Also there is one case that cannot be solved nicely with stubbing - parent-child relation within one entity class - like this:

@Entity
data class TestEntity(
        val testEntity: TestEntity,
        @Id @GeneratedValue val id: Long? = null
) {
    private constructor() : this(STUB)

    companion object {
        val STUB = TestEntity()
    }
}

This code will produce NullPointerException due to chicken-egg issue - we need STUB to create STUB. Unfortunately we need to make this field nullable (or some similar solution) to make code works.

Also in my opinion having Id as last field (and nullable) is quite optimal. We shouldn't assign it by hand and let database do it for us.

I'm not saying that this is perfect solution, but I think that it leverages entity code readability and Kotlin features (e.g. null safety). I just hope future releases of JPA and/or Kotlin will make our code even more simpler and nicer.

1
3

As stated above you have to use the no no-arg plugin provided by Jetbrains.

If you are using Eclispe you may have to edit the Kotlin Compiler Settings.

Window > Preferences > Kotlin > Compiler

Activate the no-arg Plugin in the Compiler Plugins section.

See: https://discuss.kotlinlang.org/t/kotlin-allopen-plugin-doesnt-work-with-sts/13277/10

0
2

I`m using Intellij to run kotlin with micronaut and gradle kotlin, to solve this problem I needed to add these two lines into my build.gradle.kts:

plugins { 
    //This:
    id("org.jetbrains.kotlin.plugin.allopen") version "1.6.10"
    //And this:
    id("org.jetbrains.kotlin.plugin.jpa") version "1.6.10"

    id("com.github.johnrengelman.shadow") version "7.1.1"
    id("io.micronaut.application") version "3.2.2"
}
1

Similar to @pawelbial I've used companion object to create a default instance, however instead of defining a secondary constructor, just use default constructor args like @iolo. This saves you having to define multiple constructors and keeps the code simpler (although granted, defining "STUB" companion objects isn't exactly keeping it simple)

@Entity
data class TestEntity(
    val name: String = "",
    @Id @GeneratedValue val id: Int? = null
) {

    companion object {
        val STUB = TestEntity()
    }
}

And then for classes which relate to TestEntity

@Entity
data class RelatedEntity(
    val testEntity: TestEntity = TestEntity:STUB,
    @Id @GeneratedValue val id: Int? = null
)

As @pawelbial has mentioned, this won't work where the TestEntity class "has a" TestEntity class since STUB won't have been initialised when the constructor is run.

1

If you added the gradle plugin https://plugins.gradle.org/plugin/org.jetbrains.kotlin.plugin.jpa but did not work, chances are the version is out dated. I was on 1.3.30 and it didn't work for me. After I upgraded to 1.3.41(latest at time of writing), it worked.

Note: kotlin version should be the same as this plugin, eg: this is how I added both:

buildscript {
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version"
    }
}
1
  • I am working with Micronaut, and I got it to work with version 1.3.41. Gradle says my Kotlin version is 1.3.21 and I didnt see any issues, all the other plugins ('kapt/jvm/allopen') are on 1.3.21 Also I am using the plugins DSL format
    – Gavin
    Jul 19, 2019 at 17:24
0

These Gradle build lines helped me:
https://plugins.gradle.org/plugin/org.jetbrains.kotlin.plugin.jpa/1.1.50.
At least, it builds in IntelliJ. It's failing on the command line at the moment.

And I have a

class LtreeType : UserType

and

    @Column(name = "path", nullable = false, columnDefinition = "ltree")
    @Type(type = "com.tgt.unitplanning.data.LtreeType")
    var path: String

var path: LtreeType did not work.

0

As I've seen that there is no valid complete answer i'm sharing my solution:

Add this to build.gradle.kts (I'm using gradle Kotlin so change according to your format)

plugins {
    kotlin("plugin.noarg") version "1.9.21"
    kotlin("plugin.jpa") version "1.9.21"
}

buildscript {
    dependencies {
        classpath("org.jetbrains.kotlin:kotlin-noarg")
    }
}

// This is key - to actually generate noarg constructor on class marked with @Entity
noArg {
    annotation("jakarta.persistence.Entity")
}
-3

Refer to the Interface method mentioned by @tin-ng in the following thread:

hibernate jpa projection with @Query

Convert class Person(val name: String, val age: Int) to Interface Person{ val name: String val age: Int }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.