skip to content

Compiler Plugins: all-open / no-arg

The all-open plugin strips final from annotated classes and no-arg synthesizes a parameterless constructor, which is how Spring and JPA work with Kotlin's final-by-default classes. It is the standard answer to why a Kotlin entity needs the kotlin-jpa plugin.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

Kotlin classes are final by default. Why does that break Spring and JPA, and what compiler plugins solve it?

level: juniorimportance: must knowfreq 75%

answer

  1. Kotlin = final by default
  2. Spring CGLIB proxies need open classes
  3. Hibernate needs no-args ctor + open entities
  4. kotlin-spring = all-open preset
  5. kotlin-jpa = no-arg preset

basics

~20 s

Kotlin classes can't be subclassed unless you write 'open'. Spring and Hibernate need to subclass your classes to add behavior. The all-open and no-arg plugins make the needed classes open and give them an empty constructor automatically.

solid answer

~30 s

By default every Kotlin class and method is `final` (the opposite of Java). Spring creates runtime CGLIB/proxy subclasses of `@Configuration`, `@Component`, `@Transactional`, etc. to add AOP/proxy behavior, and Hibernate subclasses `@Entity` classes for lazy loading — both fail on final classes. The `all-open` (kotlin-allopen) plugin makes classes annotated with configured annotations implicitly `open`. The `kotlin-spring` preset configures all-open for Spring's stereotype annotations. Separately, JPA/Hibernate requires a no-args constructor to instantiate entities reflectively; the `no-arg` (kotlin-noarg) plugin, via the `kotlin-jpa` preset, synthesizes a (synthetic, non-public) no-args constructor for `@Entity`/`@Embeddable`/`@MappedSuperclass` classes. You apply them as Gradle plugins (`kotlin("plugin.spring")`, `kotlin("plugin.jpa")`).

code

kotlin · 8 lines
kotlin
// Without plugins this fails at runtime under Hibernate/Spring:
@Entity
class Book(           // final, has no no-args constructor
    @Id val id: Long,
    var title: String,
)
// kotlin-jpa makes it effectively open + synthesizes Book()
// kotlin-spring makes @Service/@Configuration classes open

go deeper

for a junior

Knows Kotlin is final-by-default and that kotlin-spring/kotlin-jpa exist to fix framework breakage.

for a middle

Distinguishes all-open (removes final) from no-arg (adds constructor) and names the presets.

for a senior

Explains the runtime mechanics — CGLIB proxies, Hibernate lazy proxies, reflective instantiation — driving each requirement.

for a principal

Reasons about whether to prefer final-by-default discipline, immutable design, and when to lean on plugins vs. explicit modeling.

## The problem: Kotlin is final-by-default In Kotlin, every class and member function is `final` unless you explicitly mark it `open`. This is a deliberate design choice (Effective Java's "design for inheritance or prohibit it"). Java is the opposite: everything is open unless marked `final`. Two major Java frameworks assume they can subclass your code: - **Spring** generates **proxy subclasses at runtime** (CGLIB byte-code subclasses) for things like `@Configuration` classes, `@Transactional` methods, `@Async`, and other AOP. A proxy is a generated subclass that wraps each method to add cross-cutting behavior. It cannot subclass a `final` class or override a `final` method. - **Hibernate/JPA** subclasses `@Entity` classes to implement **lazy loading** (the entity returned is actually a proxy that loads data on first access). It also instantiates entities via **reflection using a no-args constructor**. So plain Kotlin classes break both frameworks: `final` blocks proxying, and the absence of a no-args constructor blocks reflective instantiation. ## Plugin 1: all-open (kotlin-allopen) The `all-open` compiler plugin makes a class — and its members — implicitly `open` **if** the class carries one of a configured set of annotations. You don't write `open` by hand. The **`kotlin-spring`** Gradle plugin is a preset of all-open pre-configured with Spring's annotations: `@Component` (and meta-annotated `@Service`/`@Repository`/`@Controller`/`@Configuration`), `@Async`, `@Transactional`, `@Cacheable`, `@SpringBootTest`. So an `@Service class Foo` becomes effectively `open class Foo` at compile time. ## Plugin 2: no-arg (kotlin-noarg) The `no-arg` plugin **synthesizes a zero-argument constructor** for classes carrying configured annotations. The generated constructor is **synthetic** — it isn't visible in normal Kotlin/Java source and you can't call it directly; frameworks reach it via reflection. The **`kotlin-jpa`** plugin is a preset of no-arg pre-configured for `@Entity`, `@Embeddable`, and `@MappedSuperclass`. (kotlin-jpa also pulls in all-open behavior for those JPA annotations, since Hibernate proxies entities too.) ## Applying them (Gradle Kotlin DSL) ```kotlin plugins { kotlin("jvm") version "2.1.0" kotlin("plugin.spring") version "2.1.0" // all-open preset for Spring kotlin("plugin.jpa") version "2.1.0" // no-arg (+ all-open) preset for JPA } ``` ## Key terms - **final / open**: `final` = cannot be subclassed/overridden; `open` = can be. - **Proxy / CGLIB subclass**: a generated subclass wrapping your methods to inject behavior. - **Synthetic constructor**: a constructor emitted by the compiler, not present in source, callable only reflectively. - **Stereotype annotation**: Spring's `@Component`-family markers.

  • Does all-open make every class in the project open?
    No — only classes annotated with a configured annotation (or meta-annotated by one). Untouched classes stay final.
  • If you mark a Spring bean class final by hand, what happens?
    all-open's effect overrides the default finality based on the annotation, so it still compiles open; but you generally shouldn't fight the plugin.

all-open unlocks a door frameworks need to walk through (subclassing); no-arg hands them a key to start the engine (a constructor) without you writing either.

saying these in an interview costs you the question

  • Claiming Kotlin classes are open by default (they're final)
  • Saying you must manually write 'open' on every entity/bean
  • Confusing the two plugins: all-open does NOT add a constructor
  • Thinking the plugins are runtime libraries rather than compile-time plugins
  • Believing all classes become open globally

context

open as a page

Precisely, which problem does all-open solve and which does no-arg solve? Map kotlin-spring and kotlin-jpa onto them.

level: middleimportance: must knowfreq 65%

basics

~20 s

all-open removes 'final' so classes can be subclassed. no-arg adds an empty constructor so frameworks can create the object. kotlin-spring is an all-open preset for Spring; kotlin-jpa is a no-arg preset (plus all-open) for JPA entities.

open as a page

How would you configure all-open / no-arg for your own custom annotations rather than the Spring/JPA presets?

level: middleimportance: should knowfreq 40%

basics

~20 s

Apply the base allopen or noarg plugin and list your own annotations in its Gradle config block. Any class with those annotations then becomes open, or gets an empty constructor, just like the Spring/JPA presets do.

open as a page

A teammate writes JPA entities as Kotlin `data class`. Why is that problematic even with kotlin-jpa applied, and what entity-modeling guidance follows?

level: seniorimportance: should knowfreq 45%

basics

~20 s

kotlin-jpa makes entities open and gives them an empty constructor, but data classes auto-generate equals/hashCode/copy from all properties. With lazy loading and mutable IDs that breaks equality and causes surprises, so plain classes are recommended for entities.

open as a page

At what stage do all-open / no-arg operate, and what are the consequences for tooling, final-by-design, and modules that don't apply them?

level: seniorimportance: nice to knowfreq 28%

basics

~20 s

They run inside the Kotlin compiler while building each module, changing the generated bytecode. They aren't libraries and don't run at runtime. A module only gets the effect if it applies the plugin, and the change is invisible in your source.

open as a page