{"id":595295,"date":"2025-09-04T12:56:15","date_gmt":"2025-09-04T11:56:15","guid":{"rendered":"https:\/\/blog.jetbrains.com\/?post_type=idea&#038;p=595295"},"modified":"2025-10-26T23:05:55","modified_gmt":"2025-10-26T22:05:55","slug":"improved-annotation-handling-in-kotlin-2-2-less-boilerplate-fewer-surprises","status":"publish","type":"idea","link":"https:\/\/blog.jetbrains.com\/pt-br\/idea\/2025\/09\/improved-annotation-handling-in-kotlin-2-2-less-boilerplate-fewer-surprises","title":{"rendered":"Improved Annotation Handling in Kotlin 2.2: Less Boilerplate, Fewer Surprises\u00a0"},"content":{"rendered":"\n<p>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.<\/p>\n\n\n\n<p>This often means annotations don\u2019t land where they\u2019re 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\u2019s really going on. For example, validation might not be enforced when entities are loaded or updated from the database.<\/p>\n\n\n\n<p>With Kotlin 2.2, we\u2019re addressing this problem. A new default rule makes annotations land where developers expect them to, reducing boilerplate and aligning better with popular frameworks.<\/p>\n\n\n\n<p>Consider this simple JPA entity:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">@Entity\nclass Order(\n\u00a0\u00a0\u00a0\u00a0@Id\n\u00a0\u00a0\u00a0\u00a0@GeneratedValue\n\u00a0\u00a0\u00a0\u00a0val id: Long,\n\n\u00a0\u00a0\u00a0\u00a0@NotBlank\n\u00a0\u00a0\u00a0\u00a0var name: String,\n\n\u00a0\u00a0\u00a0\u00a0@Email\n\u00a0\u00a0\u00a0\u00a0var email: String\n)<\/pre>\n\n\n\n<p>At first glance this looks correct. But in Kotlin versions before 2.2, the default rule applied these annotations only to the constructor parameter (<code>@param<\/code>).<\/p>\n\n\n\n<p>If we look at the decompiled code, it actually becomes:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">public class Order {\n\u00a0\u00a0@Id\n\u00a0\u00a0@GeneratedValue\n\u00a0\u00a0private final long id;\n\n\u00a0\u00a0@NotNull\n\u00a0\u00a0private String name;\n\n\u00a0\u00a0@NotNull\n\u00a0\u00a0private String email;\n\n  public Order(long id, @NotBlank @NotNull String name, @Email @NotNull String email) {\n      Intrinsics.checkNotNullParameter(name, \"name\");\n      Intrinsics.checkNotNullParameter(email, \"email\");\n      super();\n      this.id = id;\n      this.name = name;\n      this.email = email;\n   }\n\u2026\n}<\/pre>\n\n\n\n<p>Validation annotations like <code>@NotBlank<\/code> and <code>@Email<\/code> weren\u2019t placed on the field\/property \u2013 they only validated object construction, not property updates.<\/p>\n\n\n\n<p>This mismatch has been a common source of confusion.<\/p>\n\n\n\n<p><strong>Previous workaround: Explicit use-site targets<\/strong><\/p>\n\n\n\n<p>The fix was to explicitly mark the targets, for example with <code>@field<\/code>:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">@Entity\nclass Order(\n\u00a0\u00a0\u00a0\u00a0@field:Id\n\u00a0\u00a0\u00a0\u00a0@GeneratedValue\n\u00a0\u00a0\u00a0\u00a0val id: Long,\n\n\u00a0\u00a0\u00a0\u00a0@field:NotNull\n\u00a0\u00a0\u00a0\u00a0var name: String,\n\n\u00a0\u00a0\u00a0\u00a0@field:Email\n\u00a0\u00a0\u00a0\u00a0var email: String\n)<\/pre>\n\n\n\n<p>so that becomes:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">public class Order {\n\u00a0\u00a0@Id\n\u00a0\u00a0@GeneratedValue\n\u00a0\u00a0private final long id;\n\n\u00a0\u00a0@NotBlank\n\u00a0\u00a0@NotNull\n\u00a0\u00a0private String name;\n\n\u00a0\u00a0@Email\n\u00a0\u00a0@NotNull\n\u00a0\u00a0private String email;\n\u2026\n}<\/pre>\n\n\n\n<p>This approach works, but clutters the code with extra syntax. It also requires developers to know about Kotlin\u2019s use-site targets and remember which one each framework expects.<\/p>\n\n\n\n<p><strong>Kotlin 2.2: A new default that just works<\/strong><\/p>\n\n\n\n<p>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.<\/p>\n\n\n\n<p>That means our original code now works as expected, without requiring any additional annotations:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">@Entity\nclass Order(\n\u00a0\u00a0\u00a0\u00a0@Id\n\u00a0\u00a0\u00a0\u00a0@GeneratedValue\n\u00a0\u00a0\u00a0\u00a0val id: Long,\n\n\u00a0\u00a0\u00a0\u00a0@NotBlank\n\u00a0\u00a0\u00a0\u00a0var name: String,\n\n\u00a0\u00a0\u00a0\u00a0@Email\n\u00a0\u00a0\u00a0\u00a0var email: String\n)<\/pre>\n\n\n\n<p><strong>With the new rule:<\/strong><\/p>\n\n\n\n<ul>\n<li>Bean validation annotations are present on the property, so validation applies on updates as well as on constructions.<\/li>\n\n\n\n<li>The code looks like clean and idiomatic Kotlin, without repetitive <code>@field<\/code>: syntax or surprising behavior.<\/li>\n<\/ul>\n\n\n\n<p><strong>How to enable the new behavior<\/strong><\/p>\n\n\n\n<p>? Kotlin 2.2 is required.<\/p>\n\n\n\n<p>By default, the compiler issues warnings if your code\u2019s behavior may change under the new rule.<\/p>\n\n\n\n<p>In IntelliJ IDEA, you can also use the quick-fix on a warning to enable the new default project-wide.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" loading=\"lazy\" width=\"1600\" height=\"248\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/09\/image-3.png\" alt=\"\" class=\"wp-image-596599\"\/><\/figure>\n\n\n\n<p>To switch fully to the new behavior in your project, enable it in IntelliJ IDEA via Gradle\u2019s settings.<\/p>\n\n\n\n<p>Add the following to your <code>build.gradle.kts<\/code>:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">kotlin {\n\u00a0\u00a0\u00a0\u00a0compilerOptions {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0freeCompilerArgs.add(\"-Xannotation-default-target=param-property\")\n\u00a0\u00a0\u00a0\u00a0}\n}\n\nIf you prefer to keep the old behavior, you can use:\n\nkotlin {\n\u00a0\u00a0\u00a0\u00a0compilerOptions {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0freeCompilerArgs.add(\"-Xannotation-defaulting=first-only\")\n\u00a0\u00a0\u00a0\u00a0}\n}<\/pre>\n\n\n\n<p>Or stay in a transitional mode with warnings:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">kotlin {\n\u00a0\u00a0\u00a0\u00a0compilerOptions {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0freeCompilerArgs.add(\"-Xannotation-defaulting=first-only-warn\")\n\u00a0\u00a0\u00a0\u00a0}\n}<\/pre>\n\n\n\n<p><strong>Why this matters<\/strong><\/p>\n\n\n\n<p>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\u2019s also a step toward making Kotlin\u2019s integration with major frameworks smoother.<\/p>\n\n\n\n<p>More details can be found in<a href=\"https:\/\/youtrack.jetbrains.com\/issue\/KT-73255\" target=\"_blank\" rel=\"noopener\"> KT-73255<\/a> and <a href=\"https:\/\/github.com\/Kotlin\/KEEP\/blob\/main\/proposals\/KEEP-0402-annotation-target-in-properties.md\" target=\"_blank\" rel=\"noopener\">KEEP-402<\/a>.<\/p>\n\n\n\n<p>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 Spring even better.<\/p>\n\n\n\n<p>Happy coding!<\/p>\n","protected":false},"author":1423,"featured_media":595326,"comment_status":"closed","ping_status":"closed","template":"","categories":[4113],"tags":[378,8904,21],"cross-post-tag":[],"acf":[],"_links":{"self":[{"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/idea\/595295"}],"collection":[{"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/idea"}],"about":[{"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/types\/idea"}],"author":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/users\/1423"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/comments?post=595295"}],"version-history":[{"count":9,"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/idea\/595295\/revisions"}],"predecessor-version":[{"id":653001,"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/idea\/595295\/revisions\/653001"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/media\/595326"}],"wp:attachment":[{"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/media?parent=595295"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/categories?post=595295"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/tags?post=595295"},{"taxonomy":"cross-post-tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/cross-post-tag?post=595295"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}