In JPA, how does the provider decide whether to read and write an entity's state through its fields or through its getters and setters, and why does the placement of the @Id annotation matter for that decision?
answer
- @Id placement sets the default for the hierarchy
- field on field, getter on getter — mixing is silently ignored
- @Access overrides at class or member level
- PROPERTY = your getters/setters run during load and flush
- defensive-copy getter → phantom dirty updates
basics
~20 sAccess type is decided by where the mapping annotations — specifically @Id or @EmbeddedId — are placed. On fields it is FIELD access; on getters it is PROPERTY access, and the provider then calls getters and setters instead of touching fields. @Access overrides the default.
solid answer
~50 sThere are two access types. With `AccessType.FIELD` the provider reads and writes the fields directly by reflection; with `AccessType.PROPERTY` it calls `getX()`/`setX()`. The default is inferred from **where the identifier annotation sits**: `@Id` on the field means FIELD access for the whole entity hierarchy; `@Id` on the getter means PROPERTY access. You can override it explicitly with `@Access(AccessType.FIELD)` at class level, or per attribute on a member. The trap is that annotations placed on the *other* kind of member are simply ignored — no error. Put `@Id` on the field and `@Column(name = "first_name")` on the getter and the column mapping silently disappears; you get a column named by the default naming strategy. Practically, FIELD access is the common choice: it keeps mapping out of the API, avoids getters with side effects being invoked during load and flush, and stops defensive-copy getters from making dirty checking see spurious changes.
go deeper
Know that annotations go on fields or on getters, that the @Id placement decides which, and that you must be consistent within a class.
Explain the inference rule, the silent-ignore trap with a concrete example, and the class-level versus member-level @Access override.
Bring the runtime consequences — setters invoked during hydration, defensive-copy getters causing phantom dirty checks, identifier access on proxies — and state a team-wide convention.
Treat it as an encapsulation policy decision: field access keeps persistence out of the domain API, and any need for property access is a signal that the persisted representation and the domain representation have diverged and may want an explicit translation layer.
## The two access types JPA has to move state between the row and the object. It can do that two ways. - **Field access** (`AccessType.FIELD`): the provider uses reflection on the declared fields — `field.setAccessible(true)`, then get and set. Your getters and setters are never involved in persistence. - **Property access** (`AccessType.PROPERTY`): the provider calls the JavaBean getter to read state and the setter to write it. Every load, every flush comparison, every merge goes through your methods. Which one applies is a per-entity-hierarchy decision, not a per-class whim, and it also determines *which annotations the provider even looks at*. ## How the default is chosen The rule in the specification: the placement of the identifier mapping determines the default access type for the entity hierarchy. If `@Id` (or `@EmbeddedId`) is on a field, the hierarchy defaults to FIELD access and the provider reads mapping annotations from fields. If it is on a getter, the hierarchy defaults to PROPERTY access and the provider reads annotations from getters. This is why the identifier's placement matters far beyond the identifier itself: it silently decides the parsing rule for every other mapping annotation in the class, and for subclasses too. A `@MappedSuperclass` with `@Id` on a field pushes FIELD access down to entities that extend it unless they override it. ## The silent-ignore trap ```java @Entity public class Person { @Id private Long id; // → FIELD access private String firstName; @Column(name = "first_name") // ← on the getter: IGNORED public String getFirstName() { return firstName; } } ``` Nothing fails. The provider simply never inspects the getter, so `firstName` maps to whatever the naming strategy produces from the field name. Depending on the strategy, the column may come out as `firstName` or `first_name` — and the bug surfaces as a schema-validation error or a missing column at runtime, far from the annotation you wrote. The same trap catches `@Enumerated`, `@Temporal`, `@Convert`, `@Lob` and fetch settings. The discipline is: **pick one placement and put every mapping annotation there.** ## Overriding with @Access `@Access` can be applied at class level to state the access type outright — `@Access(AccessType.FIELD)` — which is worth doing when a `@MappedSuperclass` or an `@EmbeddedId` class makes the inferred default non-obvious. It can also be applied to a single member to make just that attribute use the other access type, which is the standard way to run a mostly-field-access entity with one computed, converted or normalized attribute. ## Runtime consequences you should be able to name **Getter side effects.** With property access, Hibernate calls `getX()` during dirty checking at flush and `setX()` while hydrating a row. A setter that trims strings, uppercases, lazily initializes a collection, or fires domain events will run at times you did not choose. A getter that returns `new ArrayList<>(items)` or a defensive copy of a `Date` can make the entity look dirty on every flush, producing a stream of pointless `UPDATE` statements. **Null handling on load.** With property access, a setter that rejects nulls or validates input will throw while Hibernate hydrates a row that legitimately contains a null. Field access sidesteps validation entirely, which is usually what you want for reconstruction from the database. **Proxies and the identifier.** With property access, Hibernate knows the identifier getter method and its proxy can answer `getId()` without hitting the database, because the identifier is already known from the foreign key. With field access, calling `getId()` on an uninitialized proxy runs your own method body, which reads the field and generally forces initialization. (Bytecode enhancement changes this picture, so treat it as a tendency, not a law.) **Encapsulation.** Field access lets you keep the class's public API clean: no setter needs to exist just because a column does, and immutable-ish entities with only business methods are possible. ## Which to choose Field access is the pragmatic default in most codebases, for exactly the reasons above. Property access is worth reaching for when the persisted representation genuinely differs from the in-memory one and you want a method to mediate the translation — though a converter or a single `@Access`-overridden attribute usually expresses that better than flipping the whole entity.
- An entity has @Id on the field but @Column(name = "email_address") on the getter. What actually happens?The @Id placement makes the hierarchy FIELD access, so the provider never inspects the getters and the @Column is ignored entirely. The attribute maps to whatever column name the naming strategy derives from the field. No warning is issued, so the symptom is usually a schema-validation failure or an unexpected column at runtime.
- Why can property access cause unnecessary UPDATE statements?At flush time Hibernate compares the current state against the loaded snapshot, and with property access it reads current state through your getter. If the getter returns a new object each call — a defensive copy of a collection, array or Date — the comparison fails equality and the entity is treated as dirty. The result is an UPDATE on every flush even though nothing changed.
- Where do you put @Access when an entity uses an @EmbeddedId?The access type of the embeddable follows the access type in force where it is used, which can be surprising when the embeddable class annotates its own members differently. Declaring @Access(AccessType.FIELD) explicitly on both the entity and the embeddable removes the ambiguity and stops annotations in the embeddable from being silently ignored.
Field access is a warehouse worker reaching straight into the box; property access is one who must hand everything through a clerk at the counter — and whatever the clerk does to the item on the way through happens on every trip.
saying these in an interview costs you the question
- Saying access type is chosen by a persistence.xml setting rather than by annotation placement.
- Believing you can freely mix field and getter annotations in one class and have both honoured.
- Claiming property access is required for lazy loading to work.
- Assuming setters are only called by application code, so validation in them is safe.
- Thinking @Access on one attribute changes the whole entity's access type.