How would you configure all-open / no-arg for your own custom annotations rather than the Spring/JPA presets?
answer
- Base plugins: plugin.allopen / plugin.noarg
- allOpen { annotation("...") } block
- noArg { annotation("..."); invokeInitializers }
- Meta-annotations propagate triggers
- Presets = base plugins with fixed lists
basics
~20 sApply 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.
solid answer
~40 sApply the base plugins — `kotlin("plugin.allopen")` and/or `kotlin("plugin.noarg")` — and declare your annotations in their extension blocks: `allOpen { annotation("com.example.Open") }` and `noArg { annotation("com.example.NoArg") }`. Every class carrying that annotation, directly **or via meta-annotation** (a custom annotation itself annotated with the trigger), is then compiled `open` / gets a synthetic no-args constructor. For no-arg you can add `noArg { invokeInitializers = true }` to run `init {}` blocks in the generated constructor (off by default, so property initializers/`init` are skipped). The `kotlin-spring` and `kotlin-jpa` presets are exactly this — base plugins with a fixed annotation list. You can also combine: apply both presets and the base plugins to extend the trigger sets. `preset("spring")` / `preset("jpa")` can be referenced inside the allOpen/noArg blocks too.
code
kotlin · 15 lines// build.gradle.kts
allOpen {
annotation("com.example.Open")
}
noArg {
annotation("com.example.NoArg")
invokeInitializers = true
}
// usage
annotation class Open
annotation class NoArg
@Open @NoArg
class Aggregate(val id: Long) // open + synthetic no-args ctorgo deeper
Knows presets exist but may not know how to wire custom annotations.
Can configure allOpen/noArg blocks with custom annotations and explain meta-annotation propagation.
Understands invokeInitializers semantics and synthetic-constructor visibility implications.
Weighs introducing project marker annotations vs. presets, and the maintenance cost of custom compiler-plugin configuration across modules.
## Why customize The presets cover Spring and JPA, but you may have your own framework, your own marker annotation, or want a single project-specific `@Open` marker. The base plugins let you supply the trigger annotation list yourself. ## all-open with custom annotations ```kotlin plugins { kotlin("jvm") version "2.1.0" kotlin("plugin.allopen") version "2.1.0" } allOpen { annotation("com.example.Open") // any class with @Open becomes open // preset("spring") // optionally also include Spring's set } ``` Define the marker normally: ```kotlin annotation class Open @Open class Service // compiled as: open class Service ``` **Meta-annotations work too:** if `@MyBean` is itself annotated `@Open`, then `@MyBean class X` is also opened. This is how `@Service` (meta-annotated `@Component`) is covered by a single `@Component` trigger. ## no-arg with custom annotations ```kotlin plugins { kotlin("plugin.noarg") version "2.1.0" } noArg { annotation("com.example.NoArg") invokeInitializers = true // optional: run init{} + property initializers } ``` ```kotlin annotation class NoArg @NoArg class Data(val x: Int, val y: String) // synthesizes a synthetic Data() constructor ``` ### invokeInitializers By default the synthesized no-args constructor **does not** run `init {}` blocks or property initializers — properties are left at their JVM default (null/0). That's correct for JPA, where Hibernate immediately populates fields. Set `invokeInitializers = true` only if you need the initializers to execute; be aware that referencing not-yet-set constructor params inside `init` can NPE. ## The presets are just configured base plugins `kotlin-spring` ≈ `kotlin-allopen` with Spring annotations preloaded; `kotlin-jpa` ≈ `kotlin-noarg` + all-open with JPA annotations preloaded. You can apply a preset AND add your own annotations in the same block. ## Synthetic constructor visibility The generated constructor is **synthetic**, meaning ordinary Kotlin/Java code cannot call it; only reflective access (which deliberately reads synthetic members) reaches it. This keeps your public API clean while satisfying frameworks.
- Why is invokeInitializers false by default?JPA/Hibernate populate fields right after construction, so running initializers is wasted work and can reference unset params. Default-off matches that lifecycle.
- How does one @Component trigger cover @Service, @Repository, etc.?Via meta-annotations — those stereotypes are themselves annotated @Component, and all-open follows annotation meta-annotation chains.
saying these in an interview costs you the question
- Thinking you must list every subclass annotation rather than relying on meta-annotations
- Assuming the synthesized constructor runs init{} blocks by default
- Believing the no-args constructor is public/callable from normal code
- Not knowing the presets are just preconfigured base plugins
- Confusing the Gradle extension block names (allOpen vs noArg)