IntelliJ IDEA
IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin
Improved Annotation Handling in Kotlin 2.2: Less Boilerplate, Fewer Surprises
Many developers have run into subtle issues with annotations when working with Kotlin and frameworks like Spring or JPA-based persistence frameworks. For instance, an annotation applied to a constructor parameter might not always end up in the property or backing field where the framework expects it.
This often means annotations don’t land where they’re needed. For example, bean validation checks might only happen when the object was first created, but not when its properties were later updated. The result? Confusing bugs, surprising runtime behavior, and sometimes even the need to dig into bytecode to see what’s really going on. For example, validation might not be enforced when entities are loaded or updated from the database.
With Kotlin 2.2, we’re addressing this problem. A new default rule makes annotations land where developers expect them to, reducing boilerplate and aligning better with popular frameworks.
Consider this simple JPA entity:
@Entity class Order( @Id @GeneratedValue val id: Long, @NotBlank var name: String, @Email var email: String )
At first glance this looks correct. But in Kotlin versions before 2.2, the default rule applied these annotations only to the constructor parameter (@param
).
If we look at the decompiled code, it actually becomes:
public class Order { @Id @GeneratedValue private final long id; @NotNull private String name; @NotNull private String email; public Order(long id, @NotBlank @NotNull String name, @Email @NotNull String email) { Intrinsics.checkNotNullParameter(name, "name"); Intrinsics.checkNotNullParameter(email, "email"); super(); this.id = id; this.name = name; this.email = email; } … }
Validation annotations like @NotBlank
and @Email
weren’t placed on the field/property – they only validated object construction, not property updates.
This mismatch has been a common source of confusion.
Previous workaround: Explicit use-site targets
The fix was to explicitly mark the targets, for example with @field
:
@Entity class Order( @field:Id @GeneratedValue val id: Long, @field:NotNull var name: String, @field:Email var email: String )
so that becomes:
public class Order { @Id @GeneratedValue private final long id; @NotBlank @NotNull private String name; @Email @NotNull private String email; … }
This approach works, but clutters the code with extra syntax. It also requires developers to know about Kotlin’s use-site targets and remember which one each framework expects.
Kotlin 2.2: A new default that just works
Starting with Kotlin 2.2, annotations without an explicit use-site target are applied to the constructor parameter and the property or field, aligning behavior with what most frameworks expect.
That means our original code now works as expected, without requiring any additional annotations:
@Entity class Order( @Id @GeneratedValue val id: Long, @NotBlank var name: String, @Email var email: String )
With the new rule:
- Bean validation annotations are present on the property, so validation applies on updates as well as on constructions.
- The code looks like clean and idiomatic Kotlin, without repetitive
@field
: syntax or surprising behavior.
How to enable the new behavior
🔗 Kotlin 2.2 is required.
By default, the compiler issues warnings if your code’s behavior may change under the new rule.
In IntelliJ IDEA, you can also use the quick-fix on a warning to enable the new default project-wide.

To switch fully to the new behavior in your project, enable it in IntelliJ IDEA via Gradle’s settings.
Add the following to your build.gradle.kts
:
kotlin { compilerOptions { freeCompilerArgs.add("-Xannotation-default-target=param-property") } } If you prefer to keep the old behavior, you can use: kotlin { compilerOptions { freeCompilerArgs.add("-Xannotation-defaulting=first-only") } }
Or stay in a transitional mode with warnings:
kotlin { compilerOptions { freeCompilerArgs.add("-Xannotation-defaulting=first-only-warn") } }
Why this matters
This change makes annotation behavior more predictable, reduces boilerplate, and eliminates a class of subtle bugs that Spring and JPA developers have faced for years. It’s also a step toward making Kotlin’s integration with major frameworks smoother.
More details can be found in KT-73255 and KEEP-402.
This update is part of our broader initiative to improve the Kotlin + Spring experience. Stay tuned for more inspections, tooling improvements, and language updates that make working with these frameworks even better.
Happy coding!