Kotlin Help

Kotlin-jpa compiler plugin

The kotlin-jpa compiler plugin configures Kotlin projects for the Java Persistence API (JPA). JPA has two requirements that conflict with Kotlin's defaults:

  • A zero-argument constructor. JPA instantiates entities through reflection (a process called hydration), which requires a zero-argument constructor. Kotlin classes don't declare one by default.

  • Non-final classes. JPA providers create proxy subclasses of your entities to support lazy loading (for example, for @ManyToOne and @OneToOne associations). Kotlin classes and their members are final by default, which prevents this. If entities aren't open, lazy loading won't work.

The kotlin-jpa plugin addresses both. It's a preconfigured wrapper that applies:

  • The no-arg plugin to generate a zero-argument constructor for JPA-annotated classes.

  • The all-open plugin to make JPA-annotated classes and their members open.

The generated constructor is annotated with @Deprecated at the HIDDEN level, so you can call it from Java but not from Kotlin. JPA can call it through reflection.

Supported JPA annotations

JPA annotations exist in two package namespaces: the older javax.persistence (Java EE) and the newer jakarta.persistence (since Jakarta EE 9, used by Spring Boot 3.x and later). The kotlin-jpa plugin recognizes both namespaces and specifies the following annotations automatically for the underlying no-arg and all-open plugins:

  • javax.persistence.Entity and jakarta.persistence.Entity

  • javax.persistence.Embeddable and jakarta.persistence.Embeddable

  • javax.persistence.MappedSuperclass and jakarta.persistence.MappedSuperclass

Use the annotations from the namespace that matches your framework version. You don't need to configure the plugin for either namespace: both are enabled by default.

Apply the plugin

Gradle

Add the kotlin-jpa plugin in the plugins {} block of your build.gradle(.kts) file:

plugins { kotlin("jvm") version "2.4.21" // The version of Kotlin to use kotlin("plugin.jpa") version "2.4.21" // The Kotlin JPA plugin }
plugins { id "org.jetbrains.kotlin.jvm" version "2.4.21" id "org.jetbrains.kotlin.plugin.jpa" version "2.4.21" }

Maven

In Maven, support for the kotlin-jpa plugin is provided by the kotlin-maven-noarg plugin. Add it to your pom.xml file:

<properties> <kotlin.version>2.4.21</kotlin.version> </properties> <plugin> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-plugin</artifactId> <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>

Use the kotlin-jpa plugin

When the plugin is applied, classes annotated with common JPA annotations become open and get a zero-argument constructor at compile time. You can write entities as regular classes without adding the open keyword or declaring a zero-argument constructor manually.

The following example uses the jakarta.persistence namespace. If your framework still uses Java EE, import the same annotations from javax.persistence instead and the plugin handles both:

import jakarta.persistence.Entity import jakarta.persistence.GeneratedValue import jakarta.persistence.Id @Entity class Message( var text: String, ) { // The kotlin-jpa plugin makes this class open and generates a // zero-argument constructor that JPA uses to instantiate it @Id @GeneratedValue var id: Long? = null }

Best practices for JPA entities

Applying the plugin makes entities open and gives them a zero-argument constructor, but a few Kotlin idioms still conflict with how JPA providers manage entities. Follow these guidelines to avoid common issues:

  • Use a regular class, not a data class. Data classes favor immutable val properties, and derive equals(), hashCode(), and toString() from all properties. For entities, this breaks proxies, and toString() or equals() can trigger extra queries on lazy associations. Model entities as regular classes (the plugin makes them open) and reserve data classes for DTOs and query results.

  • Declare mapped properties as var. JPA sets fields when hydrating an entity, so mapped properties must be mutable.

  • Make generated identifiers nullable and mutable. A @GeneratedValue identifier is null until the entity is persisted, so declare it as var id: Long? = null.

  • Base equals() and hashCode() only on the identifier and type, not on all properties. This keeps them stable across the entity lifecycle and avoids loading lazy fields.

For a detailed discussion of these pitfalls, see the How to avoid common pitfalls with JPA and Kotlin blog post.

Cover classes without JPA annotations

The kotlin-jpa plugin configures only supported JPA annotations. To generate a zero-argument constructor or make classes open for types that aren't annotated with a JPA annotation, declare your own annotation and register it with the underlying no-arg and all-open plugins:

  1. Declare your own annotation:

    package com.example annotation class OpenNoArgAnnotation
  2. In your build file, register the annotation with the underlying plugins:

    In your build.gradle.kts file, register the annotation in the noArg {} and allOpen {} blocks that are available through the kotlin-jpa plugin:

    plugins { kotlin("jvm") version "2.4.21" kotlin("plugin.jpa") version "2.4.21" } noArg { annotation("com.example.OpenNoArgAnnotation") } allOpen { annotation("com.example.OpenNoArgAnnotation") }

    In your build.gradle file, register the annotation in the noArg {} and allOpen {} blocks that are available through the kotlin-jpa plugin:

    plugins { id "org.jetbrains.kotlin.jvm" version "2.4.21" id "org.jetbrains.kotlin.plugin.jpa" version "2.4.21" } noArg { annotation("com.example.OpenNoArgAnnotation") } allOpen { annotation("com.example.OpenNoArgAnnotation") }

    In your pom.xml file, add the no-arg:annotation and all-open:annotation options to the kotlin-maven-plugin configuration:

    <properties> <kotlin.version>2.4.21</kotlin.version> </properties> <plugin> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-plugin</artifactId> <version>${kotlin.version}</version> <configuration> <compilerPlugins> <plugin>jpa</plugin> </compilerPlugins> <pluginOptions> <!-- Add multiple lines for multiple annotations --> <option>no-arg:annotation=com.example.OpenNoArgAnnotation</option> <option>all-open:annotation=com.example.OpenNoArgAnnotation</option> </pluginOptions> </configuration> <dependencies> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-noarg</artifactId> <version>${kotlin.version}</version> </dependency> </dependencies> </plugin>
  3. Mark the classes that don't have a JPA annotation:

    package com.example import jakarta.persistence.Entity import jakarta.persistence.GeneratedValue import jakarta.persistence.Id // Automatically covered by the kotlin-jpa plugin @Entity class Message( var text: String, ) { @Id @GeneratedValue var id: Long? = null } // The custom annotation makes the class open // and generates a zero-argument constructor @OpenNoArgAnnotation class MessageMetadata( var author: String, )

For more details, see no-arg and all-open.

What's next?

21 October 2026