Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
307 changes: 264 additions & 43 deletions .dev-loop/INGEST_REPORT.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions log.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@ Append-only. Format: `## [YYYY-MM-DD] <ingest|revise|lint|gap|contradiction|drif
## [2026-08-06] revise | Union-merged amendments from the same 15 PRs into 24 existing pages (largest: tests-that-cannot-fail — 7 PRs: per-assertion mutation granularity, restore-mechanism-by-commit-state, suite-total-drop detection; portable-shell-scripts — 8 PRs: POSIX inline set-- reordering, ${VAR:-} vs ${VAR-} colon semantics, quote-by-what-the-text-is; test-data-and-isolation — 6 PRs: env-derived write paths, absent-variable cases, exec-bit fixtures, leak attribution; change-impact/call-site-enumeration — 8 unique additions from 7 path-variants: rename-in-place, same-type reorder, *args forwarding, two-searches-agree fallacy, codemod, coverage completion check). All non-canonical cross-page ids remapped to canonical pages; every added line traced to its source PR hunk (fabricated worker output was detected by orphan-line verification and replaced with true PR content).
## [2026-08-06] dedup | Cross-PR duplicate clusters collapsed: client-side rate limiting (8 PR versions at 3 paths → 1), call-site enumeration (7 versions at 7 paths → additions folded into the already-merged canonical page), stderr/exit-0 diagnostics (4 versions at 4 paths → 1), macOS sysroot (2→1), env-var off switch (2→1), completion predicates (2→1), robots.txt (2→1), harness-mediated results (2→1), leaked test artifacts (2→1), orchestration category naming (orchestration vs agent-orchestration → agent-orchestration). Kept distinct after trigger comparison: differential-testing vs differential-run-agreement (setup vs interpretation), unset-versus-empty-parameters vs env-var-off-switches (semantics vs design, cross-linked), import-time-side-effects vs test-level-choice edge (tactics vs level choice, cross-linked). Merged-main near-dup scan (Jaccard over title+trigger, 141 pages): 0 pairs — no duplication among previously merged content.
## [2026-08-06] ingest | Reconciled post-consolidation flush PRs #42–#43 (created against pre-#44 main). Folded unique content: platforms/filesystems/permissions-and-exec-bits (+`sh "$SCRIPT"` interpreter-invocation rows — stubs need no exec bit, avoids EDR chmod+x heuristics), platforms/shells/command-text-inspected-before-execution (+gate-AUTHOR step 8: parse all three POSIX quoting forms, expand only ~/$HOME/${HOME}; red-then-green bats evidence), testing/quality/checks-that-cannot-pass (+vacuously-green pre-implementation test row), platforms/processes/tool-diagnostics-without-a-failing-exit-code (+repeat-suppression edge, +clang reproduction source), infrastructure/ci-cd/secrets-handling (+independent credential channels row: gh token vs git SSH vs API token, git push --dry-run / author:@me probes), testing/mocking/what-to-mock (+ESM read-only-bindings DI rows, +PATH-emptied tripwire negative-proof). Dropped as duplicates of the #44 state: #42's rate-limit row (already in timeouts-and-retries + client-side-rate-limiting), #42's warning-only-diagnostics page (same trigger as tool-diagnostics-without-a-failing-exit-code), #42's leak-attribution row (already in test-data-and-isolation).
## [2026-08-13] ingest | knowledge-flush of 3 queued insights into 2 new pages (two insights share one page — same mechanism, one case). New: backend/java/jpa/not-null-check-and-lifecycle-callbacks — attribute the `PropertyValueException: not-null property references a null or transient value` by path shape rather than by the word "transient" (one hardcoded literal is shared by both throw sites in `Nullability`, and the only production occurrence in hibernate-orm; dots come solely from `buildPropertyPath` via `checkSubElementsNullability`, which recurses into `CompositeType` and into collections of composite elements), plus the ordering fact that `DefaultFlushEntityEventListener.scheduleUpdate` checks nullability *before* queueing `EntityUpdateAction` (5.6/6.6/7.0), so no `@PreUpdate` has run and a listener-based fix cannot work; INSERT-side "or transient" traced to `nullifyTransientReferencesIfNotAlready()`; and the silent-toggle case — `TypeSafeActivator` calls `setCheckNullability(false)` when validation mode is CALLBACK/AUTO and `hibernate.check_nullability` was never set ("Defaults to disabled if Bean Validation is present in the classpath and annotations are used, or enabled otherwise"), so a dependency change flips enforcement; measure via `isCheckNullability()` or a flush-and-expect test. New: databases/data-survey/audit-columns-as-update-evidence — an audit column is evidence only about the writer that sets it (Spring Data's `AuditingEntityListener.touchForUpdate` is `@PreUpdate`), so all-NULL `update_dt` cannot separate "never updated" from "every update failed pre-flush", and bulk JPQL leaves it untouched ("The effect of an `update` or `delete` statement is not reflected in the persistence context"); bound the claim, judge history on an independent axis, and require one positive control before acting on the absence. Sources live-verified this session against hibernate-orm 5.6/6.6/7.0 sources, the ValidationSettings/SessionFactoryOptions javadocs, the Hibernate query-language guide, and spring-data-jpa; field observation from PRD `manage.building_tenant_floor_info` (574 rows).
1 change: 1 addition & 0 deletions wiki/backend/java/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ load it alongside the stack page here — these pages link the exact ids.
|------|-----------|
| [entity-mapping](jpa/entity-mapping.md) | Writing or reviewing JPA entity classes/associations — fetch types (to-one EAGER default), bidirectional sync helpers, entity equals/hashCode; debugging `LazyInitializationException`, `MultipleBagFetchException`, entities vanishing from Sets, or unexpected joins/queries traced to mappings; deciding DTO projection vs entity for read-only endpoints; evaluating Open Session in View |
| [persistence-context](jpa/persistence-context.md) | Debugging changes saved without calling save (dirty checking), stale reads within one transaction (first-level cache), flush timing surprises around queries, detached-entity errors (merge vs persist, lost updates after merge); designing or fixing slow/memory-hungry JPA batch inserts; choosing IDENTITY vs SEQUENCE id generation for batch-heavy tables |
| [not-null-check-and-lifecycle-callbacks](jpa/not-null-check-and-lifecycle-callbacks.md) | A write fails with `PropertyValueException: not-null property references a null or transient value` and you must attribute it to an attribute/path (dotted path vs plain name, embeddables, nulled transient references); about to fix such a failure in a `@PreUpdate` listener; asking why an entity's `nullable = false` is (or stopped being) enforced at runtime — `hibernate.check_nullability` vs Bean Validation on the classpath; deciding between `@Column(nullable = false)`, `@NotNull`, and the DB constraint as the enforcing layer |

## spring

Expand Down
109 changes: 109 additions & 0 deletions wiki/backend/java/jpa/not-null-check-and-lifecycle-callbacks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
---
id: backend-java-jpa-not-null-check-and-lifecycle-callbacks
domain: backend
category: jpa
applies_to: [java, kotlin, jpa, hibernate, spring]
confidence: verified
sources:
- https://github.com/hibernate/hibernate-orm/blob/5.6/hibernate-core/src/main/java/org/hibernate/engine/internal/Nullability.java
- https://github.com/hibernate/hibernate-orm/blob/6.6/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java
- https://github.com/hibernate/hibernate-orm/blob/6.6/hibernate-core/src/main/java/org/hibernate/action/internal/AbstractEntityInsertAction.java
- https://github.com/hibernate/hibernate-orm/blob/6.6/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/TypeSafeActivator.java
- https://docs.hibernate.org/orm/6.6/javadocs/org/hibernate/cfg/ValidationSettings.html
- https://docs.hibernate.org/orm/6.6/javadocs/org/hibernate/boot/spi/SessionFactoryOptions.html
- https://thorben-janssen.com/hibernate-tips-whats-the-difference-between-column-nullable-false-and-notnull/
- https://www.baeldung.com/hibernate-not-null-error
last_verified: 2026-08-13
related: [backend-java-jpa-persistence-context, backend-java-kotlin-frameworks-and-jpa, databases-schema-design-nullability-and-defaults, databases-data-survey-audit-columns-as-update-evidence]
---

# Attributing Hibernate's Not-Null Check Exception, and Knowing Whether It Runs

## When this applies

A write fails with `PropertyValueException: not-null property references a null or
transient value : <path>` and you must decide which attribute and which code path
produced it; you are about to fix it inside a `@PreUpdate` listener; or you are asking
why an entity's `nullable = false` was never enforced before (or stopped being).

Column-side nullability design → [databases-schema-design-nullability-and-defaults].
Reading the failed rows' audit columns → [databases-data-survey-audit-columns-as-update-evidence].

## Do this

1. Identify the attribute from the **path shape**, not from the words "or transient".
`Nullability` throws this message from two sites that share one hardcoded literal —
it is the only production occurrence of that string in hibernate-orm — so the
wording says nothing about which site fired:

| Path in the message | Means | Inspect |
|---------------------|-------|---------|
| No dot (`title`) | A top-level attribute of the entity held `null` when the check ran | That attribute's column mapping, and every path that assembles the entity |
| Contains a dot (`address.city`) | A sub-attribute of a composite value (`@Embeddable`) held `null`; only `checkSubElementsNullability` → `buildPropertyPath` produces dots, and it recurses into `CompositeType` | The embeddable's own `nullable = false` attributes — the owning entity attribute (`address`) was non-null |
| Contains a dot and the parent attribute is a collection | The same, reached through a collection whose **element type** is composite (`@ElementCollection` of `@Embeddable`); the first loaded non-null element decides | The element class's not-null attributes |

2. On the UPDATE path, drop `@PreUpdate`/`@PostUpdate` from both the suspect list and
the fix. `DefaultFlushEntityEventListener.scheduleUpdate` runs
`new Nullability( session ).checkNullability( values, persister, … )` and only then
adds `EntityUpdateAction` to the action queue; the callbacks fire inside that
action's `execute()`. The order is the same in 5.6, 6.6 and 7.0. So when this
exception is thrown, no `@PreUpdate` listener has run on that entity: a listener
cannot have nulled the value, and a listener cannot supply it.
3. Fix the value where the entity state is assembled — the service, mapper, or
deserializer that produced the instance — or change the declared nullability if
"absent" is a real state of the domain.
4. Before ruling an attribute out, check whether the loop even examined it.
`Nullability` skips an attribute when it is not insertable (INSERT) or not
updatable (UPDATE), when its value is `UNFETCHED_PROPERTY` (lazy, not loaded), and
when Hibernate generates the value in memory (`GenerationTiming != NEVER` — e.g.
`@CreationTimestamp`, `@UpdateTimestamp`).
5. Measure whether the check is active instead of inferring it from the mapping.
`hibernate.check_nullability` "Defaults to disabled if Bean Validation is present in
the classpath and annotations are used, or enabled otherwise":
`TypeSafeActivator.applyCallbackListeners` calls `setCheckNullability( false )`
whenever the validation mode is `CALLBACK`/`AUTO` **and** the setting has no value.
Adding or removing a dependency such as `spring-boot-starter-validation` therefore
flips it. Read it, or assert it:

| To establish | Do |
|--------------|----|
| The effective setting at startup | `emf.unwrap( SessionFactoryImplementor.class ).getSessionFactoryOptions().isCheckNullability()` |
| That the behaviour holds for this build | A test that flushes an entity whose `nullable = false` attribute is null and expects `PropertyValueException` |

6. When the app-level check must hold regardless of which dependencies are on the
classpath, set `hibernate.check_nullability=true` explicitly. `@Column(nullable =
false)` alone does not give you a Bean Validation constraint — it "adds a not null
constraint to the database column, if Hibernate creates the database table
definition" — so with the core check off, the enforcement left is the DB constraint.
Add `@NotNull` (Kotlin: `@field:NotNull` →
[backend-java-kotlin-frameworks-and-jpa]) when you want the validator to reject it.

## Edge cases

| Case | Then |
|------|------|
| The same message on `persist()`/INSERT | The check runs in `AbstractEntityInsertAction.nullifyTransientReferencesIfNotAlready()`, immediately after `nullifyTransientReferences( getState() )` — a to-one attribute holding an unsaved instance is nulled first, then reported as null. That is where "or transient" comes from; the reported path is still the plain attribute name |
| The path names an attribute whose DB column is nullable | The entity declares not-null while the column allows NULL; existing rows can already violate it, and they surface only once this check is on (directive 5) |
| No exception at all despite a null on a `nullable = false` attribute | The check is off — the statement reaches the database, where a real NOT NULL constraint raises a `ConstraintViolationException` and a nullable column accepts the row silently |
| The failing attribute is `@UpdateTimestamp`/`@CreationTimestamp` | It is skipped by the check (directive 4); the reported path belongs to another attribute |
| The write path is a bulk JPQL/HQL `update` or native SQL | No entity flush happens, so this check never runs — the database constraint is the only gate → [databases-data-survey-audit-columns-as-update-evidence] |

## Instead of

| If you are about to | Do this instead | Why |
|---------------------|-----------------|-----|
| Add a `@PreUpdate` listener that fills the missing value | Set it where the entity state is assembled (directive 3) | The check runs before `EntityUpdateAction` exists, so no `@PreUpdate` has run — the listener is never reached on the failing flush |
| Read "or transient" as evidence that an unsaved association caused it | Read the path shape (directive 1); on INSERT, treat a nulled transient reference as one of the causes | One literal is shared by both throw sites; the distinct unsaved-instance error carries its own message, "object references an unsaved transient instance" |
| Conclude the check is on because the mapping says `nullable = false` | Read `isCheckNullability()` or assert the exception in a test (directive 5) | Bean Validation on the classpath disables the core check unless the setting is explicit |
| Set `hibernate.check_nullability=false` to get past the exception | Supply the value, or relax the declared nullability | Disabling moves the failure to the DB constraint, or writes the incomplete row when the column is nullable |

## Sources

- https://github.com/hibernate/hibernate-orm/blob/5.6/hibernate-core/src/main/java/org/hibernate/engine/internal/Nullability.java — two throw sites share the literal `"not-null property references a null or transient value"`; the dotted path comes only from `buildPropertyPath(...)` via `checkSubElementsNullability`, which recurses into `CompositeType` and into collections whose element type is composite; the loop skips non-checkable, `UNFETCHED_PROPERTY`, and `GenerationTiming != NEVER` attributes; comment: "Typically when Bean Validation is on, we don't want to validate null values at the Hibernate Core level. Hence the checkNullability setting."
- https://github.com/hibernate/hibernate-orm/blob/6.6/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java — `scheduleUpdate`: "check nullability but do not doAfterTransactionCompletion command execute" → `new Nullability( session ).checkNullability(...)` precedes `new EntityUpdateAction(...)` (same order on 5.6 and 7.0)
- https://github.com/hibernate/hibernate-orm/blob/6.6/hibernate-core/src/main/java/org/hibernate/action/internal/AbstractEntityInsertAction.java — `nullifyTransientReferencesIfNotAlready()` nullifies transient references and then runs the CREATE-type nullability check
- https://github.com/hibernate/hibernate-orm/blob/6.6/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/TypeSafeActivator.java — "de-activate not-null tracking at the core level when Bean Validation is present unless the user explicitly asks for it": guarded by validation mode `CALLBACK`/`AUTO`, then `if ( cfgService.getSettings().get( CHECK_NULLABILITY ) == null ) … setCheckNullability( false )`
- https://docs.hibernate.org/orm/6.6/javadocs/org/hibernate/cfg/ValidationSettings.html — `CHECK_NULLABILITY`: "Enable nullability checking, raises an exception if an attribute marked as not null is null at runtime"; "Defaults to disabled if Bean Validation is present in the classpath and annotations are used, or enabled otherwise"
- https://docs.hibernate.org/orm/6.6/javadocs/org/hibernate/boot/spi/SessionFactoryOptions.html — `boolean isCheckNullability()` exposes the effective setting
- https://thorben-janssen.com/hibernate-tips-whats-the-difference-between-column-nullable-false-and-notnull/ — `@Column(nullable = false)` "adds a not null constraint to the database column, if Hibernate creates the database table definition" and otherwise leaves validation to the database; `@NotNull` is what Bean Validation checks on pre-persist/pre-update
- https://www.baeldung.com/hibernate-not-null-error — the widely repeated two-cause framing this page corrects: the same message attributed to "a null value for a column marked with nullable = false" and to "an association referencing an unsaved instance", with no mention of the path shape
Loading
Loading