skip to content

When would you map a column with the JPA @Column attributes insertable = false and updatable = false, and what does the provider do differently once you set them?

level: middleimportance: should knowfreq 45%

answer

  1. removed from INSERT/UPDATE, still in SELECT
  2. DB defaults only fire if the column is absent from INSERT
  3. FK mapped twice → scalar copy read-only, association writes
  4. updatable=false = write-once column
  5. after insert the attribute is stale until refresh

basics

~20 s

They remove the column from generated INSERT and UPDATE statements while it is still read on SELECT. Use them for database-maintained columns and for a foreign key mapped twice — once as an association, once as a plain field. The in-memory value can go stale.

solid answer

~50 s

`insertable = false` drops the column from the generated `INSERT`; `updatable = false` drops it from the generated `UPDATE`. Reads are unaffected — the column is still selected and hydrated into the attribute. Three standard uses: 1. **Database-maintained columns** — a `created_at` with a `DEFAULT`, a value set by a trigger, a column owned by another application. Setting both flags tells the provider to keep its hands off. 2. **A foreign key mapped twice.** When you want both `@ManyToOne Customer customer` and a plain `Long customerId` for the same column, exactly one mapping may own the write. The scalar copy gets `@Column(name = "customer_id", insertable = false, updatable = false)`. 3. **Write-once columns** — `updatable = false` alone on something like `created_by` makes it immutable after insert, so a stray setter cannot rewrite history. The main gotcha is staleness: after an insert, the in-memory attribute holds whatever Java had (often null) while the database holds the generated value, until you re-read the entity.

go deeper

for a junior

Know that the flags exclude the column from INSERT and UPDATE while reads still work.

for a middle

Give the two main use cases — database-owned columns and a foreign key mapped twice — and explain the repeated-column bootstrap error they resolve.

for a senior

Cover the staleness after insert and the concrete remedies, plus the silent-no-op hazard of updatable=false for later writers.

for a principal

Frame it as declaring column ownership across application and database, and require that any use come with an explicit answer for how the in-memory value stays truthful.

## What the flags actually do Hibernate builds one `INSERT` and one `UPDATE` statement per entity at bootstrap, listing the columns of every writable attribute. `insertable = false` removes the column from the first list, `updatable = false` from the second. Nothing else changes: the column is still part of the `SELECT`, and the attribute is still hydrated from the row on load. The flags are about *writing*, never about reading. Because the statements are built once at bootstrap, this is a static decision — the column is either in the statement for every entity of that type or in none of them. There is no conditional variant. ## Use case 1: columns the database owns Some columns are not the application's business to write: - `created_at timestamptz NOT NULL DEFAULT now()` — the default only applies if the column is absent from the `INSERT`. If the provider includes it with a Java `null`, the default never fires and you get a null (or a constraint violation). - Columns maintained by triggers, or by a second application, or computed by the database. `@Column(insertable = false, updatable = false)` is what makes the provider omit the column and let the database do its job. ## Use case 2: the same foreign key mapped twice This is the case most often asked about. You have an association and you also want the raw key without touching the association — to avoid loading the other entity, to build a response payload, to filter in code: ```java @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "customer_id") private Customer customer; @Column(name = "customer_id", insertable = false, updatable = false) private Long customerId; ``` Two attributes now point at one column. Without the flags the provider sees the column listed twice in the write statements and fails at bootstrap with a repeated-column error. Marking the scalar copy read-only resolves the ownership: the association writes, the scalar reads. The cost is a consistency trap. Set `customer` to a different entity and `customerId` still holds the old value in memory until the entity is reloaded — the two attributes are not kept in sync by anything. Teams that use this pattern keep the scalar strictly read-only in their own code too, and never expose a setter for it. ## Use case 3: write-once columns `updatable = false` on its own gives you an immutable-after-insert column: `created_at` written by the application, `created_by`, a natural key that must never change. The provider will silently ignore any later change to the attribute — which is both the feature and the hazard, since a developer who changes the value and sees no `UPDATE` has to know why. ## The staleness problem After `persist()` and flush, the persistence context holds an entity whose read-only attributes were never sent and never re-read. A `created_at` filled by a database default is `null` in memory while the row has a timestamp. Any code in the same transaction that reads it gets the wrong answer, and if the entity is serialized into a response, the client sees the wrong answer. The blunt fix is `EntityManager.refresh(entity)`, which issues a `SELECT` and re-hydrates from the row — correct, but an extra round trip and it discards other unflushed changes to that instance. The cleaner options are to compute the value in Java (so nothing is generated database-side), to have the provider re-read generated values automatically via its generated-value support, or to accept the staleness and only read the field after the transaction, from a freshly loaded entity. ## Related gotchas - These flags are **not** the same as marking an entity immutable; other columns still update normally. - They do not stop *you* from changing the field in Java. The change is simply never written, which can look like a silent data-loss bug to whoever wrote the setter. - With optimistic version checking, a column excluded from `UPDATE` never participates in the write, so a change to it cannot bump anything — reinforcing that the database is the owner. - On a `@JoinColumn` the same two attributes exist and behave the same way; the read-only duplicate pattern is the mirror image of the one above when the scalar is the writer. ## Rule of thumb Use the flags to express ownership: *this column is written by someone other than the entity's normal write path*. Whenever you set them, ask immediately how the in-memory value stays truthful, and answer it deliberately rather than by accident.

  • Why does mapping both a @ManyToOne and a scalar Long onto the same column fail without insertable=false, updatable=false?
    The provider builds its INSERT and UPDATE statements from the writable attributes, and two writable attributes bound to one column would list that column twice, so bootstrap fails with a repeated-column error. Marking one of them read-only resolves the conflict by declaring which mapping owns the write. The read-only copy is still hydrated on SELECT, so it is usable for reads.
  • After inserting an entity whose created_at column has a database DEFAULT and is mapped insertable=false, the field is null in memory. What are your options?
    Call EntityManager.refresh() to re-read the row, at the cost of a round trip and losing unflushed changes to that instance. Alternatively, have the provider re-read database-generated values automatically, or set the timestamp in Java and drop the database default so there is one owner. The worst option is to ignore it and let the stale null reach a response payload.

saying these in an interview costs you the question

  • Believing the flags make the column read-only in Java too.
  • Expecting a database DEFAULT to apply while the column is still listed in the INSERT.
  • Keeping a writable setter for a read-only duplicated foreign-key field.
  • Assuming the in-memory value is refreshed automatically after an insert.
  • Thinking updatable=false makes the whole entity immutable.

context