What is the difference between marking a field with the JPA @Transient annotation and with the Java `transient` keyword, and what does the JPA @Basic annotation actually control?
answer
- default = everything persists; you opt out
- @Transient: no column, still serialized
- transient keyword: no column AND not serialized
- @Basic implicit; optional = hint, not enforcement
- @Basic(fetch=LAZY) needs bytecode enhancement
basics
~20 sBoth exclude a field from persistence, but the Java keyword also excludes it from Java serialization while @Transient does not. @Basic is optional metadata for simple attributes: optional=false is a nullability hint and fetch=LAZY is only a hint too.
solid answer
~50 sBy default every non-static, non-final field of an entity is persistent — you opt *out*, not in. `@Transient` is the JPA opt-out: the attribute is not mapped to any column. It says nothing about Java serialization, so a `@Transient` field still travels when the object is serialized. The Java `transient` keyword also makes the attribute non-persistent per the specification, but its primary meaning is the serialization one — the field is skipped by Java serialization. Using it to control persistence conflates two concerns, so prefer `@Transient` and use the keyword only when you genuinely mean "do not serialize". `@Basic` marks a simple attribute explicitly; it is implicit on every mapped simple field, so you rarely write it. Its two attributes are `optional`, a nullability hint the provider may use for DDL and validation, and `fetch`, where `LAZY` is only a hint — Hibernate honours lazy basic attributes only with bytecode enhancement, and otherwise loads the column with the row.
go deeper
Know that fields are persistent by default, that @Transient opts out, and that @Basic is optional.
Draw the two-axis distinction between persistence and Java serialization, and explain that @Basic's optional and fetch are hints.
Explain why lazy basic attributes need bytecode enhancement, what the extra query costs, and the structural alternative of splitting the heavy column into its own table.
Frame default-persistent mapping as a change-control risk — every new field on an entity is implicitly a schema change — and argue for review conventions and schema validation on startup to catch it.
## The default is "everything is persistent" JPA maps by exclusion. Once a class is an entity, every field (under field access) or every property with a getter (under property access) is persistent unless something excludes it. Static and final members are excluded automatically. Everything else — including the `int counter` you added for a cache-hit metric — becomes a column, and if the schema is generated, a column appears; if the schema is validated, validation fails against a column that does not exist. This is why the exclusion annotations matter more than the inclusion ones. ## @Transient `@Transient` marks an attribute as non-persistent. Typical uses: - derived values computed from other attributes (`fullName`, `age` from a birth date); - caches or memoised results held on the instance; - flags used by application code during a unit of work; - under property access, any getter that is a computation rather than state — otherwise the provider tries to map it and demands a setter and a column. What it does *not* do is affect Java serialization. A `@Transient` field is a perfectly ordinary Java field; serialize the entity and the value goes with it. That is often exactly what you want (a derived value you would rather not recompute on the other side) but it surprises people who assume the two mechanisms are one. ## The Java `transient` keyword The keyword's job in Java is serialization: the field is skipped by `ObjectOutputStream` and comes back as the type's default on deserialization. JPA additionally specifies that a field declared `transient` is not persistent, so it works as an exclusion too. The two overlap but are not interchangeable: | | mapped to a column? | included in Java serialization? | |---|---|---| | plain field | yes | yes | | `@Transient` field | no | yes | | `transient` field | no | no | | both | no | no | Because the keyword carries a second meaning, using it purely to say "don't persist this" hides intent from the next reader — and it also changes behaviour if the entity is ever serialized into a distributed cache or an HTTP session. Use `@Transient` for persistence intent; add the keyword only when non-serialization is also intended. ## @Basic `@Basic` is the mapping annotation for simple attributes — primitives and their wrappers, `String`, `BigDecimal`, `BigInteger`, dates and times, enums, byte arrays. It is implicit: a mapped simple field behaves as if annotated `@Basic` even when it is not. Writing it explicitly is a style choice, sometimes used to make "yes, I meant this to be persistent" visible. It carries two attributes: **`optional`** (default `true`) declares whether the attribute may be null. It is described in the specification as a *hint*. Providers may use it when generating DDL and may perform a runtime check, but it is not a validation framework: for enforced nullability, the authoritative mechanism is the `NOT NULL` constraint in the schema, and for application-level validation, a Bean Validation constraint. Note that `@Column(nullable = false)` and `@Basic(optional = false)` overlap; teams normally pick one and stay consistent. **`fetch`** (default `EAGER`) can be set to `LAZY` to ask that the column not be read until the attribute is touched. This is also only a hint, and it is the one people most often over-trust. Plain Hibernate loads whatever the `SELECT` returned; to defer a single column it must intercept the field read, which requires **build-time bytecode enhancement** of the entity. Without enhancement, `@Basic(fetch = LAZY)` on a large `String` or `byte[]` changes nothing at all. Even with enhancement, touching the attribute later issues an extra query, so the win only exists when the column is large and usually unread. The alternative that always works is structural: move the heavy column to its own entity and table behind a lazy one-to-one, or query a projection that omits it. ## Practical rules Annotate exclusions deliberately — a new field on an entity is a schema change until you say otherwise. Prefer `@Transient` for persistence intent. Do not rely on `@Basic(fetch = LAZY)` unless enhancement is actually configured and you have verified the SQL. And remember that under property access, every getter is a candidate attribute, so computed getters need `@Transient` too.
- You add @Basic(fetch = FetchType.LAZY) to a large String column and the SQL is unchanged. Why?Lazy fetching of a basic attribute requires the provider to intercept reads of that field, which Hibernate can only do with build-time bytecode enhancement of the entity class. Without enhancement the setting is ignored and the column is selected with the rest of the row. The reliable alternatives are to move the column into a separate entity behind a lazy one-to-one, or to select a projection that omits it.
- Under property access, what happens to a getter like getFullName() that concatenates two fields?The provider treats every getter as a persistent property, so it will try to map fullName to a column and will also need a setter. Annotating the getter @Transient excludes it. This is a common source of mapping errors when a team switches an entity from field to property access.
saying these in an interview costs you the question
- Saying @Transient prevents the field from being serialized.
- Saying the Java transient keyword has no effect on JPA mapping.
- Believing @Basic must be written for a field to be persisted.
- Trusting @Basic(fetch = LAZY) to defer a column without bytecode enhancement.
- Treating @Basic(optional = false) as runtime validation that will reject nulls.