skip to content

A teammate annotates a JPA/Jackson entity field with a bare constraint and it is silently ignored at runtime. Explain the cause and how `@field:` fixes it.

level: middleimportance: must knowfreq 50%

answer

  1. Bare annotation -> ctor param, not field
  2. JPA/Jackson reflect over the FIELD by default
  3. @field: redirects to the backing field
  4. @get: for getter/property access
  5. No error, just silently ignored

basics

~10 s

The bare annotation landed on the constructor parameter, but the framework reads the backing field by reflection. Since the field has no annotation, nothing happens. Prefixing with @field: puts it where the framework looks.

solid answer

~40 s

Frameworks like Hibernate/JPA and Jackson typically discover annotations by reflecting over **fields** (`field access`). In a Kotlin primary-constructor property, a bare annotation resolves to the **constructor parameter** first (priority `param > property > field`), so the backing field stays unannotated. The reflective scan sees an unannotated field and ignores the constraint — no error, just wrong behavior. Writing `@field:Column(...)`, `@field:NotNull`, or `@field:JsonProperty(...)` forces the annotation onto the backing field where the framework actually reads it. For getter-based access (e.g. Hibernate property access or Bean Validation on the getter) you'd use `@get:` instead. The fix is matching the target to the element the specific framework reflects over.

code

kotlin · 5 lines
kotlin
@Entity
class Product(
    @field:Id val id: Long,
    @field:Column(length = 20) val sku: String,
)

go deeper

for a junior

Recognizes that the annotation may have landed on the wrong element and that @field: exists.

for a middle

Explains the param>field default order and correctly chooses @field: for field-access frameworks.

for a senior

Distinguishes field vs getter/property access per framework and verifies the access type rather than guessing.

for a principal

Establishes entity/DTO conventions and lint/review checks so the whole codebase avoids the silent-miss pitfall.

## The failure mode Consider a JPA entity written the idiomatic Kotlin way: ```kotlin @Entity class Product( @Id val id: Long, @Column(length = 20) val sku: String, // BUG: lands on the ctor parameter ) ``` The `@Column(length = 20)` here resolves to the **constructor parameter**, because Kotlin's default target order is `param > property > field` and `@Column` is applicable on a parameter. JPA with **field access** reflects over the backing `sku` field — which carries **no** annotation — so the `length = 20` is ignored and the column is created with the default length. There is no compile error and no warning at runtime: a classic silent boundary pitfall. ## Why this happens (mechanics) 1. One property -> backing field + getter + ctor parameter. 2. A bare annotation picks exactly one element from `param > property > field` (first applicable). 3. `param` is an annotation on the *constructor*, which most ORMs/serializers do not scan. ## The fix: use-site `@field:` ```kotlin @Entity class Product( @field:Id val id: Long, @field:Column(length = 20) val sku: String, // now on the backing field ) ``` Now the annotation is emitted on the backing field, which JPA's field-access scanner reads. The same applies to Jackson: ```kotlin data class Dto( @field:JsonProperty("user_name") val userName: String, ) ``` ## When to use `@get:` instead If the framework uses **property/getter access** (e.g. Hibernate `@Access(AccessType.PROPERTY)`, or Bean Validation configured to validate getters), the annotation must be on the getter: ```kotlin @get:NotNull val token: String ``` ## Rule of thumb - Field-access ORM / Jackson default -> `@field:` - Getter/property-access -> `@get:` - Bean Validation -> commonly works on the field, but verify; mismatched targets silently disable the constraint.

  • How would you decide between `@field:` and `@get:` for a given annotation?
    Match the target to how the framework reads annotations: field access -> `@field:`, property/getter access -> `@get:`. Check the framework's documented access type.
  • Does adding `@field:` change the public API as seen from Java?
    It changes only which element carries the annotation; the field stays private. It does not alter method signatures, but it can change framework-visible metadata.

saying these in an interview costs you the question

  • Blaming the framework version instead of the annotation target
  • Adding @get: when the framework uses field access (or vice versa)
  • Thinking the bug needs a compiler flag rather than a target prefix
  • Claiming bare annotations are always wrong (they default correctly for some cases)

context