hibernate/hibernate-orm · error · AuditException

@Changelog.ChangesetId property '<changesetIdProperty>' is n

Error message

@Changelog.ChangesetId property '<changesetIdProperty>' is null after persisting changelog entity '<changelogClassName>'

What it means

ChangelogSupplier persists the changelog entity in a child session sharing the parent's connection, flushes, then reads the @Changelog.ChangesetId property (or the @Id when the property is the id). If the value is still null - the id was neither generated nor assigned - AuditException is thrown, because the changeset id is required to stamp every audit row of that changeset.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/audit/spi/ChangelogSupplier.java:189

	 * from the changelog entity after persistence.
	 * <p>
	 * Handles both regular properties and {@code @Id} properties.
	 */
	private Object readChangesetId(
			Object changelog,
			EntityPersister persister,
			SharedSessionContractImplementor session) {
		final Object changesetId;
		final var changesetIdAttr = persister.findAttributeMapping( changesetIdProperty );
		if ( changesetIdAttr != null ) {
			changesetId = persister.getValue( changelog, changesetIdAttr.getStateArrayPosition() );
		}
		else {
			// @ChangesetId is the @Id
			changesetId = persister.getIdentifier( changelog, session );
		}
		if ( changesetId == null ) {
			throw new AuditException(
					"@Changelog.ChangesetId property '" + changesetIdProperty
					+ "' is null after persisting changelog entity '"
					+ changelogClass.getName() + "'"
			);
		}
		return changesetId;
	}

	/**
	 * Persist the changelog entity using a child {@link Session}
	 * that shares the parent session's JDBC connection.
	 * The child session is returned so it can be kept open
	 * for deferred flush of {@code @ElementCollection} changes.
	 */
	private static Session persistChangelog(
			SharedSessionContract session,
			Object changelog) {
		final var childSession = session.sessionWithOptions()

View on GitHub (pinned to fad1729dce)

Solutions

  1. Annotate the changeset id with @GeneratedValue (sequence/identity) so it is populated on insert, or assign it yourself before persist.
  2. Prefer sequence-based or UUID/assigned ids so the id exists at insert time.
  3. If using identity generation, confirm the JDBC driver returns generated keys for the changelog table.

Example fix

// before - id never populated
@Changelog.ChangesetId
Long changesetId; // null after flush -> AuditException
// after
@Changelog.ChangesetId @GeneratedValue
Long changesetId; // or set the value manually before persist
Defensive patterns

Strategy: validation

Validate before calling

// with assigned ids, populate the changeset id before flush
if (revEntity.getChangesetId() == null) {
    revEntity.setChangesetId(nextId());
}

Prevention

When it happens

Trigger: A changelog entity whose changeset-id property is mapped without a generator and never set before persist; an identity/trigger generator whose value is not returned on insert; a generator whose assignment happens later than the post-flush read.

Common situations: Hand-built changelog entities with a plain Long field and no @GeneratedValue; drivers that do not support getGeneratedKeys for the table; custom @GenericGenerator strategies that defer id assignment.

Related errors


AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22). Data as JSON: /api/errors/e95565dc58ead5f3. Report an issue: GitHub.